﻿using AutoMapper;
using Mercury.Core.BaseProvider;
using Mercury.Core.Configuration;
using Mercury.Core.Enums;
using Mercury.Core.Extensions;
using Mercury.Core.Utilities;
using Mercury.Entity.DomainModels;
using Mercury.Entity.SystemModels;
using Microsoft.AspNetCore.Components;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.Extensions.Logging;
using Microsoft.VisualBasic;
using OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime;
using OfficeOpenXml.FormulaParsing.Excel.Functions.Text;
using OfficeOpenXml.Style;
using StackExchange.Redis;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace Mercury.Core.Common
{
    public static class ProductMaterialCalculationHelper
    {
        private static object _locker = new object();

        public static bool IsCanExcute = true;
        #region 领料单计算
        /// <summary>
        /// 产品用料计算
        /// </summary>
        /// <param name="productName"></param>
        /// <param name="requiredWeight"></param>
        /// <param name="computedRate"></param>
        /// <returns></returns>
        public static async Task<(bool, string, List<MaterialAmountBOM>)> MaterialCalculation<T>(this string productName, string orderNo, double requiredWeight, IRepository<T> repository, bool roughComputed = true, double computedRate = 1) where T : BaseEntity
        {
            #region 1.0 获取产品计算信息
            var product = await repository.FindAsyncFirst<ProductData>(c => c.Name == productName);
            if (product == null) return new(false, "该产品信息未配置，获取产品信息失败！", null);
            var productDetails = await repository.FindAsync<ProductMaterials>(c => c.ProductId == product.Id && (!c.DeletionFlag.HasValue || c.DeletionFlag == 0));

            if (!productDetails.Any())
                return new(false, "该产品物料清单信息未配置！", null);

            var msaterialElementes = await repository.FindAsync<MaterialElement>(c => productDetails.Select(c => c.MaterialsId).Contains(c.MaterialId) && (!c.DeletionFlag.HasValue || c.DeletionFlag == 0));
            if (!msaterialElementes.Any())
                return new(false, "该产品物料元素清单信息未配置！", null);

            var elementBOMList = await repository.FindAsync<ProductElementBOM>(c => c.ProductId == product.Id && (!c.DeletionFlag.HasValue || c.DeletionFlag == 0));
            if (!elementBOMList.Any())
                return new(false, "该产品产品元素清单信息未配置！", null);
            //设置组合元素的计算元素
            else if (elementBOMList.Any(c => !string.IsNullOrEmpty(c.CalculatingElement)))
            {
                var baseElements = await repository.FindAsync<BaseElement>(c => c.Enable == 1 || !c.Enable.HasValue);
                foreach (var item in elementBOMList.Where(c => !string.IsNullOrEmpty(c.CalculatingElement)))
                {
                    var baseElement = baseElements.FirstOrDefault(c => c.ElementSymbols.Trim().ToLower() == item.CalculatingElement.Trim().ToLower());
                    if (baseElement == null)
                        return (false, $"未找到该计算元素【{item.CalculatingElement}】的基础元素配置信息！", null);
                    item.ElementId = baseElement.Id;
                }
            }
            #endregion


            #region 2.0 计算产品所需材料
            var mainComponents = msaterialElementes.GroupBy(c => c.MaterialId).Select(c => c.ToList().OrderByDescending(x => x.Content).FirstOrDefault()).ToList();
            //未参与计算的元素,含有此元素的原材料打折
            var elementBOMs = elementBOMList.Where(c => !mainComponents.Any(x => x.ElementId == c.ElementId)).ToList();
            #endregion

            Retry:
            var tempRequiredWeight = requiredWeight;

            List<ReturnedMaterial> returnMaterialList = null;
            //获取返回料 精配料时需要返回料已入库,占用所有同品返回料
            if (roughComputed)
            {
                returnMaterialList = await (from r in repository.DbContext.Set<ReturnedMaterial>().Where(c => c.Name == productName && c.Enable == 1 && c.DeletionFlag != 1)
                                            where (string.IsNullOrEmpty(r.OrderFormId) || r.OrderFormId == orderNo) && (r.State <= 5 ||
                                            (r.State == 9 && r.OrderFormId == orderNo))
                                            select new ReturnedMaterial
                                            {
                                                Category = r.Category,
                                                Id = r.Id,
                                                Weight = r.Weight,
                                                ProductNo = r.ProductNo,
                                                Name = r.Name,
                                                State = r.State
                                            }).ToListAsync();


                //returnMaterialList = await repository.DbContext.Set<ReturnedMaterial>().Join(detialsQueryable, t => new { t.ProductNo, BatchNumber = t.Category }, s => new { ProductNo = s.MaterialName, s.BatchNumber }, (t, s) => new ReturnedMaterial
                //{
                //    Category = t.Category,
                //    Id = t.Id,
                //    Weight = t.Weight,
                //    ProductNo = t.ProductNo  ,
                //    Name = t.Name,
                //    State = t.State
                //}).Where(c => c.Name == productName && c.Enable == 1 && (c.State < 5 || )).FirstOrDefaultAsync();

                //returnMaterialList =
                //    await repository.FindAsyncFirst<ReturnedMaterial>(c => c.Name == productName && c.Enable == 1 && (c.State < 5));
            }
            if (returnMaterialList != null)
                tempRequiredWeight -= returnMaterialList.Sum(c => c.Weight).GetDouble();
            var result = Computed(msaterialElementes, elementBOMList, productDetails, tempRequiredWeight, computedRate, roughComputed, elementBOMs, mainComponents, true);
            if (result.Item1 && returnMaterialList != null)
            {
                lock (_locker)
                {
                    //原本为9状态的是当前计划单占用的返回料
                    var tempList = returnMaterialList.Where(c => c.State != 9);
                    if (tempList.Any())
                    {
                        //重新获取这些返回料的状态
                        tempList = repository.Find<ReturnedMaterial>(c => tempList.Select(x => x.Id).Contains(c.Id)).ToList();
                        //存在其他占用的，则重试
                        if (tempList != null && tempList.Any(c => c.State == 9))
                        {
                            goto Retry;
                        }
                    }
                    //返回料记录
                    foreach (var item in returnMaterialList)
                    {
                        item.State = 9;
                        item.OrderFormId = orderNo;
                        repository.Update(item, new string[] { "State", "OrderFormId" });
                        result.Item3.Add(new MaterialAmountBOM()
                        {
                            BatchNO = item.Category,
                            MaterialName = item.ProductNo,
                            RequiredWeight = item.Weight.GetDouble()
                        });
                    }
                }
            }
            return result;
        }

        private static (bool, string, List<MaterialAmountBOM>) Computed(List<MaterialElement> msaterialElementes, List<ProductElementBOM> elementBOMList, List<ProductMaterials> productDetails, double requiredWeight, double computedRate, bool roughComputed, List<ProductElementBOM> pbList, List<MaterialElement> mainComponents, bool isFirst = false)
        {
            var data = new double[productDetails.Count, productDetails.Count];
            var resultData = new Matrix(productDetails.Count, 1);
            var total = requiredWeight.GetDouble() * computedRate;
            var elementBOMs = pbList;
            //找出主物料
            var mainMaterialList = elementBOMList.Join(msaterialElementes,
                s => s.ElementId, t => t.ElementId, (s, t) => new
                {
                    t.MaterialId,
                    s.TargetProportion,
                    t.Content,
                    t.ElementId
                }).OrderByDescending(c => c.TargetProportion).ThenByDescending(c => c.Content);
            var mainMaterial = mainMaterialList.FirstOrDefault();

            var groupData = mainMaterialList.GroupBy(c => c.MaterialId).Where(c => c.Key != mainMaterial.MaterialId && c.Any(x => elementBOMs.Any(c => c.ElementId == x.ElementId))).Select(c => c.ToList().OrderByDescending(x => x.Content).FirstOrDefault()).ToList();
            for (int i = 0; i < mainComponents.Count; i++)
            {
                var element = mainComponents[i];
                var tmpBom = elementBOMList.First(c => c.ElementId == element.ElementId);
                var elementRate = GetElementRate(tmpBom, groupData.Any(c => c.ElementId == element.ElementId), isFirst);
                var elementLimit = GetLimit(tmpBom);
                var elementAmount = elementRate * total;
                resultData[i, 0] = elementAmount;
                for (int j = 0; j < productDetails.Count; j++)
                {
                    var material = productDetails[j];
                    var yieldRate = material.YieldRate.GetDouble() != 0 ? material.YieldRate.GetDouble() / 100 : 1;
                    yieldRate = yieldRate.GetYeildRate(msaterialElementes, material.MaterialsId, element.ElementId);
                    var materialElement = msaterialElementes.FirstOrDefault(c => c.MaterialId == material.MaterialsId && c.ElementId == element.ElementId);
                    if (materialElement == null)
                    {
                        data[i, j] = 0;
                        continue;
                    }
                    var tmpRate = materialElement.Content.GetDouble() * yieldRate;
                    data[i, j] = tmpRate;
                }
            }
            var sourceData = new Matrix(data);
            var result = sourceData.Inverse() * resultData;

            var list = new List<MaterialAmountBOM>();
            for (int i = 0; i < result.Rows; i++)
            {
                var material = productDetails[i];
                var detial = new MaterialAmountBOM
                {
                    MaterialId = material.MaterialsId,
                    MaterialName = material.MaterialsName,
                    RequiredWeight = roughComputed ? GetWeight(result[i, 0], total) : result[i, 0].ToString("F2").GetDouble()
                };
                list.Add(detial);
            }

            //数据校验
            if (!roughComputed)
            {
                var limitDic = CheckElement(list, elementBOMList, msaterialElementes, productDetails);
                if (limitDic.Any())
                {
                    if (isFirst)
                        return Computed(msaterialElementes, elementBOMList, productDetails, requiredWeight, computedRate, roughComputed, pbList.Where(c => limitDic.ContainsKey(c.ElementId)).ToList(), mainComponents);
                    var eb = elementBOMList.Where(c => limitDic.ContainsKey(c.ElementId));
                    var msg = new StringBuilder(200);
                    foreach (var b in eb)
                    {
                        var limit = GetLimit(b);
                        msg.Append($"元素【{b.ElementName}】，占比【{limitDic[b.ElementId].ToString("F4")}】超出范围：【{limit.Item2}-{limit.Item1}】");
                    }
                    return (false, msg.ToString(), null);
                }
            }

            return (true, "", list);
        }

        /// <summary>
        /// 检测元素含量
        /// </summary>
        /// <param name="materialAmounts"></param>
        /// <param name="elementBOMs"></param>
        /// <param name="materialElements"></param>
        /// <param name="pmList"></param>
        /// <returns></returns>
        private static Dictionary<string, double> CheckElement(List<MaterialAmountBOM> materialAmounts, List<ProductElementBOM> elementBOMs, List<MaterialElement> materialElements, List<ProductMaterials> pmList)
        {
            var total = 0d;
            var elementDic = new Dictionary<string, double>();
            foreach (var material in materialAmounts)
            {
                var pm = pmList.First(c => c.MaterialsId == material.MaterialId);
                foreach (var element in materialElements.Where(c => c.MaterialId == material.MaterialId))
                {
                    var amount = material.RequiredWeight * element.Content.GetDouble() / 100;
                    if (elementDic.ContainsKey(element.ElementId))
                        elementDic[element.ElementId] += amount;
                    else
                        elementDic.Add(element.ElementId, amount);
                    total += amount;
                }
            }
            var dic = new Dictionary<string, double>();
            foreach (var item in elementBOMs)
            {
                var rate = elementDic[item.ElementId] / total * 100;
                var limit = GetLimit(item);
                if (limit.Item1 == 0)
                    continue;
                if (rate > limit.Item1 || rate < limit.Item2)
                {
                    dic.Add(item.ElementId, rate);
                }
            }
            return dic;
        }

        #endregion

        #region 配料单计算

        #region 配料辅助
        class Material
        {
            public string MaterialId { get; set; }
            public string BatchNumber { get; set; }
        }

        class ElementWeight : Material
        {
            public string ElementId { get; set; }

            public double Weight { get; set; }
        }

        class MaterialUsedStock : Material
        {
            public double UsedStock { get; set; }
        }
        class ElementRate : Material
        {
            public string ElementId { get; set; }
            public double Content { get; set; }
            public bool Check { get; set; }
            public string BoxNo { get; internal set; }
        }

        private static object lockIngredientLis = new object();
        private static IEnumerable<object> collection;

        #endregion
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="orderNo"></param>
        /// <param name="repository"></param>
        /// <param name="computedRate"></param>
        /// <param name="createBill"></param>
        /// <returns></returns>
        public static async Task<(bool, string, List<MaterialAmountBOM>)> IngredientListCalculation<T>(this string orderNo, IRepository<T> repository, bool residueFirst = false, double computedRate = 1, bool createBill = false, Func<NotificationForm, Task> sendNoticeAction = null) where T : BaseEntity
        {
            if (!CanExcute())
            {
                return (false, "计算进行中，请勿重复操作！", null);
            }
            try
            {
                var lastBill = repository.DbContext.Set<IngredientList>().FirstOrDefault(c => c.IFPDId == orderNo && c.DeletionFlag != 1);
                if (lastBill != null && lastBill.State == 5)
                    return (false, $"该生产计划生成的配料单【{lastBill.BatchingNo}】已确认，请勿重复操作！", null);
                #region 1.0 获取产品计算信息
                //1.0 获取生产计划数据
                var inductionFurnacePD = await repository.FindAsyncFirst<InductionFurnacePD>(c => c.Id == orderNo && (!c.State.HasValue || c.State == 0));
                if (inductionFurnacePD == null)
                    return (false, "生产数据不存在或已处理！", null);
                orderNo = inductionFurnacePD.OrderFormNo;
                //1.1 获取领料清单，通过领料清单获取库存
                var srOrder = await (from sro in repository.DbContext.Set<StoresRequisitionOrder>().Where(c => c.OrderNumber == orderNo)
                                     join sr in repository.DbContext.Set<StoresRequisition>().Where(c => c.DeletionFlag != 1)
                                     on sro.SRId equals sr.Id
                                     select sr
                               ).FirstOrDefaultAsync();
                if (srOrder == null)
                    return (false, "领料清单数据异常！", null);

                //1.2 获取产品名称
                var productName = inductionFurnacePD.ProductName;
                //1.3 获取生产计划需求重量
                double requiredWeight = inductionFurnacePD.RequiredWeight.GetDouble();

                //2.0 获取产品主数据
                var product = await repository.FindAsyncFirst<ProductData>(c => c.Name == productName);
                if (product == null) return new(false, "该产品信息未配置，获取产品信息失败！", null);
                //2.1 物料主表
                var materialQueryable = repository.DbContext.Set<MaterialMaster>().Where(c => !c.DeletionFlag.HasValue || c.DeletionFlag == 0);

                //2.2 获取产品明细物料数据
                var productDetailsQueryable = repository.DbContext.Set<ProductMaterials>().Where(c => c.ProductId == product.Id && (!c.DeletionFlag.HasValue || c.DeletionFlag == 0)).Join(materialQueryable, t => t.MaterialsId, s => s.Id, (t, s) => new
                {
                    t.MaterialsId,
                    t.MaterialsName,
                    t.ProductId,
                    t.YieldRate,
                    s.Category
                });
                //2.3 产品物料
                var productDetails = productDetailsQueryable.ToList();
                //2.4 判断物料信息数据
                if (!productDetails.Any())
                    return new(false, "该产品物料清单信息未配置！", null);

                //3.0 读取配料区和配料备料区库存
                var ingredientsAreaStockQueryable = repository.DbContext.Set<IngredientsArea>().Where(c => c.State == 1).Join(productDetailsQueryable, t => t.BatchingMaterials, s => s.MaterialsId, (t, s) => new
                {
                    s.MaterialsId,
                    s.MaterialsName,
                    t.BatchingBatch,
                    t.BatchingRemainingWeight,
                    t.BatchingMaterial,
                    t.MaterialPreparationWeight,
                    t.MaterialPreparationBatch,
                    t.MaterialPreparationMaterial
                });
                //3.1 配料区库存数据
                var ingredientsAreaStock = ingredientsAreaStockQueryable.ToList();
                //3.2 立库料箱库存数据
                var stockMaterialBoxQueryable = repository.DbContext.Set<MaterialBox>().Where(c => c.OrderNumber == srOrder.SRNumber && (c.State == 10 || (c.State == 99 && residueFirst)));
                var lastBillStcokMaterialBox = new List<string>();
                if (Sys_ConfigKeys.LockStock.GetConfig() == "1")
                {
                    //lastBillStcokMaterialBox = (from r in stockMaterialBoxQueryable
                    //join d in repository.DbContext.Set<BatchingMaterialBox>().Where(c => c.OrderNumber != (lastBill.Id ?? ""))
                    //on r.BoxNo equals d.MaterialBoxNo
                    //select r.BoxNo).ToList();
                    var lastBillId = lastBill?.Id ?? "";
                    lastBillStcokMaterialBox = repository.DbContext.Set<BatchingMaterialBox>().Join(stockMaterialBoxQueryable, t => t.MaterialBoxNo, s => s.BoxNo, (t, s) => new { t.MaterialBoxNo, t.OrderNumber, t.Types }).Where(c => c.OrderNumber != lastBillId).Select(c => c.MaterialBoxNo).ToList();
                }
                var stock = stockMaterialBoxQueryable.Select(c => new
                {
                    c.BoxNo,
                    c.BatchNumber,
                    c.MaterialId,
                    c.MaterialName,
                    c.MaterialWeight,
                    State = c.State ?? 0                  //99=余料 10=在库 -1=配料区 -2=备料区
                }).ToList();

                foreach (var item in lastBillStcokMaterialBox)
                {
                    var tmpStock = stock?.FirstOrDefault(c => c.BoxNo == item);
                    if (tmpStock != null)
                        stock.Remove(tmpStock);
                }

                if (!stock.Any())
                    return new(false, "计划单物料库存数据异常！", null);
                //3.3 合并所有物料批号，准备拿检化验结果
                var stockQueryable = stockMaterialBoxQueryable.Select(c => new { c.MaterialId, c.BatchNumber })
                    .Union(ingredientsAreaStockQueryable.Select(c => new { MaterialId = c.MaterialsId, BatchNumber = c.BatchingBatch }))
                    .Union(ingredientsAreaStockQueryable.Select(c => new { MaterialId = c.MaterialsId, BatchNumber = c.MaterialPreparationBatch }))
                    .GroupBy(c => new { c.MaterialId, c.BatchNumber })
                    .Select(c => new { c.Key.MaterialId, BatchNumber = c.Key.BatchNumber });
                //3.4 合并立库，配料区，配料备料区 库存
                foreach (var item in ingredientsAreaStock)
                {
                    //3.4.1 配料区 库存
                    stock.Add(new
                    {
                        BoxNo = item.BatchingMaterial,
                        BatchNumber = item.BatchingBatch,
                        MaterialId = item.MaterialsId,
                        MaterialName = item.MaterialsName,
                        MaterialWeight = item.BatchingRemainingWeight,
                        State = -1
                    });
                    //3.4.2 配料备料区 库存
                    stock.Add(new
                    {
                        BoxNo = item.MaterialPreparationMaterial,
                        BatchNumber = item.MaterialPreparationBatch,
                        MaterialId = item.MaterialsId,
                        MaterialName = item.MaterialsName,
                        MaterialWeight = item.MaterialPreparationWeight,
                        State = -2
                    });
                }

                //3.5 判断是否存在无库存的物料
                var stockGroup = stock.GroupBy(c => c.MaterialId);
                var notExistsMaterial = productDetails.FirstOrDefault(c => !stockGroup.Any(x => x.Key == c.MaterialsId));
                if (notExistsMaterial != null)
                    return (false, $"物料【{notExistsMaterial.MaterialsName}】库存不足！", null);

                //4.0 获取元素基础数据
                var baseElements = await repository.FindAsync<BaseElement>(c => c.Enable == 1 || !c.Enable.HasValue);

                //5.0 获取物料同批次检化验单号
                var queryableList = repository.DbContext.Set<CheckList>().Where(c => c.Category == 1).Join(stockQueryable, s => new { s.MaterialId, BatchNumber = s.BatchNo }, t => new { t.MaterialId, t.BatchNumber }, (s, t) => new
                {
                    t.BatchNumber,
                    t.MaterialId,
                    s.Id,
                    s.GetTime
                }
                ).ToList();

                //5.1 同批次物料检化验结果取最后一次
                var checkList = queryableList.GroupBy(c => new { c.MaterialId, c.BatchNumber }).Select(c => new
                {
                    c.Key.MaterialId,
                    c.Key.BatchNumber,
                    c.OrderByDescending(x => x.GetTime).First().Id
                }).ToList();
                //5.2 获取检测单明细
                var checkListDetaild = await repository.FindAsync<CheckListDetailed>(c => checkList.Select(x => x.Id).Contains(c.CheckListId));
                //5.3 关联获取物料批次检化验结果 元素成分占比
                var msaterialCheckList = checkListDetaild.Join(checkList, s => s.CheckListId, t => t.Id, (s, t) => new ElementRate
                {
                    MaterialId = t.MaterialId,
                    BatchNumber = t.BatchNumber,
                    ElementId = s.ElementId,
                    Check = true,
                    Content = s.Result.GetDouble()
                }).ToList();
                //5.4 获取内设物料数据的基础元素占比数据（该物料批号没有检化验结果时用基础设定结果）
                //var baseMaterialElementes = await repository.FindAsync<MaterialElement>(c => productDetails.Select(x => x.MaterialsId).Contains(c.MaterialId));
                var baseMaterialElementes = await (from p in repository.DbContext.Set<MaterialElement>().Where(c => c.DeletionFlag != 1)
                                                   join d in repository.DbContext.Set<ProductMaterials>().Where(c => c.ProductId == product.Id && c.DeletionFlag != 1)
                                                   on p.MaterialId equals d.MaterialsId
                                                   select p).GroupBy(c => new MaterialElement
                                                   {
                                                       Content = c.Content,
                                                       ElementSymbols = c.ElementSymbols,
                                                       ElementId = c.ElementId,
                                                       ElementName = c.ElementName,
                                                       Id = c.Id,
                                                       MaterialId = c.MaterialId,
                                                       YieldRate = c.YieldRate,
                                                       ReplenishmentYieldRate = c.ReplenishmentYieldRate

                                                   }).Select(c => c.Key).ToListAsync();


                //6.0 获取产品 元素控制数据
                var elementBOMList = await repository.FindAsync<ProductElementBOM>(c => c.ProductId == product.Id && (!c.DeletionFlag.HasValue || c.DeletionFlag == 0));
                if (!elementBOMList.Any())
                    return new(false, "该产品产品元素清单信息未配置！", null);
                //6.1 设置组合元素的计算元素
                else if (elementBOMList.Any(c => !string.IsNullOrEmpty(c.CalculatingElement)))
                {
                    foreach (var item in elementBOMList.Where(c => !string.IsNullOrEmpty(c.CalculatingElement)))
                    {
                        var baseElement = baseElements.FirstOrDefault(c => c.ElementSymbols.Trim().ToLower() == item.CalculatingElement.Trim().ToLower());
                        if (baseElement == null)
                            return (false, $"未找到该计算元素【{item.CalculatingElement}】的基础元素配置信息！", null);
                        item.ElementId = baseElement.Id;
                    }
                }
                #endregion    
                //残存元素
                var residualMaterialElements = new List<ElementWeight>();
                //返回料元素
                var returnMaterialElements = new List<ElementWeight>();
                bool isFirst = true;
                var residualElementTotalWeight = 0d;
                InductionFurnacePD lastTimeInductionFurnacePD = null;
                var residualResult = await ResidualCalculation();
                if (!residualResult.Item1)
                    return (residualResult.Item1, residualResult.Item2, null);
                var returnMaterialRealWeight = 0d;
                //7.0 获取返回料数据
                var returnMaterial = await repository.FindAsyncFirst<ReturnedMaterial>(c => c.OrderFormId == orderNo && c.State >= 9 && c.DeletionFlag != 1
                && (c.Heat == 0 || c.Heat == inductionFurnacePD.Heat));

                if (returnMaterial != null)
                {
                    residualResult = await ReturnedMaterialCalculation(returnMaterial);
                    if (!residualResult.Item1)
                        return (residualResult.Item1, residualResult.Item2, null);
                }
                bool removeStock = true;
                //重新计算
                ReComputed:
                var usedBatchMaterials = new List<MaterialAmountBOM>();
                var msaterialElementes = new List<ElementRate>();
                var allMaterialElementes = new List<ElementRate>();


                //8.0 获取当前计算的物料批号 元素成分信息
                var tempResult = GetMsaterialElementes(msaterialElementes);
                if (!tempResult.Item1)
                    return (tempResult.Item1, tempResult.Item2, null);
                msaterialElementes = tempResult.Item3;

                //8.1 获取物料主元素信息
                var mainComponents = msaterialElementes.GroupBy(c => c.MaterialId).Select(c => c.ToList().OrderByDescending(x => x.Content).FirstOrDefault()).ToList();
                //8.2 不可检出项
                var elementBOMs = elementBOMList.Where(c => !mainComponents.Any(x => x.ElementId == c.ElementId)).ToList();

                var tempRequiredWeight = requiredWeight;

                //8.4 需求重量扣除返回料
                //tempRequiredWeight -= returnMaterialRealWeight;
                if (tempRequiredWeight <= 0)
                    return (false, "返回料数据超标", null);

                tempRequiredWeight = tempRequiredWeight * computedRate;
                List<ProductElementBOM> newBom = null;

                //8.5 找出主物料
                var mainMaterialList = elementBOMList.Join(msaterialElementes,
                    s => s.ElementId, t => t.ElementId, (s, t) => new
                    {
                        t.MaterialId,
                        s.TargetProportion,
                        t.Content,
                        t.ElementId
                    }).OrderByDescending(c => c.TargetProportion).ThenByDescending(c => c.Content);
                var mainMaterial = mainMaterialList.FirstOrDefault();
                var groupData = mainMaterialList.GroupBy(c => c.MaterialId).Where(c => c.Key != mainMaterial.MaterialId && c.Any(x => elementBOMs.Any(c => c.ElementId == x.ElementId))).Select(c => c.ToList().OrderByDescending(x => x.Content).FirstOrDefault()).ToList();

                //9.0 换品残留计算,首次计算才计算
                tempRequiredWeight += residualElementTotalWeight;
                //9.1 换品后需要初始化元素占比
                newBom = RerunProductElementBOMAndRequiredWeight(tempRequiredWeight, GetElementWeight());

                var pds = productDetails.Select(c => new ProductMaterials
                {
                    MaterialsId = c.MaterialsId,
                    ProductId = c.ProductId,
                    MaterialsName = c.MaterialsName,
                    YieldRate = c.YieldRate
                }).ToList();
                Retry:
                //10.0 计算物料
                var result = Computed(msaterialElementes, newBom ?? elementBOMList, pds, tempRequiredWeight, mainComponents, baseMaterialElementes);
                if (result.Item1)
                {
                    //10.1 合并结果
                    var excuteResult = MergeResult(result.Item3);
                    //10.2 说明未计算结束，需要换批次或料箱继续计算
                    if (excuteResult == 1)
                    {
                        //10.2.1 重新计算产品元素占比
                        newBom = RerunProductElementBOMAndRequiredWeight(tempRequiredWeight, GetElementWeight());
                        //10.2.2 重新计算物料元素占比
                        tempResult = GetMsaterialElementes(msaterialElementes);
                        if (!tempResult.Item1)
                            return (tempResult.Item1, tempResult.Item2, null);
                        msaterialElementes = tempResult.Item3;
                        goto Retry;
                    }
                    //10.2 粗算立库库存超出需要库存，库存变动，重新计算
                    else if (excuteResult == 2)
                    {
                        goto ReComputed;
                    }

                    #region 残留元素总量计算
                    //10.3 残留元素总量计算 =》 计算完成，扣除残存总量物料
                    //不可以扣除， =》 按料箱开占用，扣除后会导致料箱占用异常
                    //if (residualElementTotalWeight > 0)
                    {
                        ////10.3.1 计算残留总量需要的原料
                        //result = Computed(msaterialElementes, elementBOMList, pds, residualElementTotalWeight, mainComponents, baseMaterialElementes);
                        ////10.3.2 优先扣除配料备料区，配料区
                        //var tmpStock = usedBatchMaterials.Join(stock, t => t.MaterialBoxNO, s => s.BoxNo, (t, s) => new
                        //{
                        //    t.MaterialId,
                        //    t.BatchNO,
                        //    t.MaterialBoxNO,
                        //    t.MaterialName,
                        //    t.RequiredWeight,
                        //    s.State
                        //});
                        ////10.3.3 扣除使用量
                        //foreach (var item in result.Item3)
                        //{
                        //    var tempMaterial = tmpStock.Where(c => c.MaterialId == item.MaterialId && c.BatchNO == item.BatchNO
                        //    && stock.Any(x => x.BoxNo == c.MaterialBoxNO && x.State < 0)).OrderBy(x => x.State).FirstOrDefault();
                        //    var material = usedBatchMaterials.FirstOrDefault(c => c.MaterialBoxNO == tempMaterial.MaterialBoxNO);
                        //    material.RequiredWeight -= item.RequiredWeight.ToString("F2").GetDouble();
                        //}
                    }
                    #endregion
                    //10.4 检验元素占比
                    var checkResult = CheckResult();
                    //10.5 首次检测不通过才重新计算，重新计算按内控下限计算，如果还超过比例内控范围 =》 配方单异常
                    if (isFirst && !checkResult.Item1)
                    {
                        isFirst = false;
                        goto ReComputed;
                    }
                    //10.6 创建配料单
                    if (createBill)
                    {
                        var createResult = CreateIngredientList();
                        if (!createResult.Item1)
                            return (createResult.Item1, createResult.Item2, usedBatchMaterials);
                    }
                    else
                        result.Item3 = usedBatchMaterials;
                }
                return result;
                #region 辅助函数
                //1.0 重新计算元素占比
                List<ProductElementBOM> RerunProductElementBOMAndRequiredWeight(double requiredWeight, List<ElementWeight> elementWeights)
                {
                    var listBom = elementBOMList.Clone();
                    if (!elementWeights.Any())
                        return listBom;
                    foreach (var item in listBom)
                    {
                        var element = elementWeights.FirstOrDefault(c => c.ElementId == item.ElementId);
                        if (element == null) continue;
                        var baseElementBom = elementBOMList.First(c => c.ElementId == item.ElementId);
                        item.TargetProportion = ((requiredWeight * GetElementRate(baseElementBom, groupData.Any(c => c.ElementId == element.ElementId), isFirst) / 100 - element.Weight) / (requiredWeight) * 100).ToDecimal();
                    }
                    return listBom;
                }

                //2.0 获取元素总重量
                List<ElementWeight> GetElementWeight()
                {
                    var tempList = residualMaterialElements.Clone();

                    #region 返回料
                    foreach (var ele in returnMaterialElements)
                    {
                        var element = tempList.FirstOrDefault(c => c.ElementId == ele.ElementId);
                        if (element == null)
                        {
                            tempList.Add(new ElementWeight
                            {
                                ElementId = ele.ElementId,
                                Weight = ele.Weight
                            });
                        }
                        else
                            element.Weight += ele.Weight;
                    }
                    #endregion

                    #region 已使用的原料合金
                    foreach (var item in usedBatchMaterials.GroupBy(c => new { c.MaterialId, c.BatchNO }).Select(c => new
                    {
                        c.Key.MaterialId,
                        c.Key.BatchNO,
                        RequiredWeight = c.Sum(c => c.RequiredWeight)

                    }))
                    {
                        var elements = allMaterialElementes.Where(c => c.MaterialId == item.MaterialId && item.BatchNO == c.BatchNumber);
                        var material = productDetails.First(c => c.MaterialsId == item.MaterialId);
                        var yieldRate = ((material.YieldRate.HasValue ? material.YieldRate.Value : 100) / 100).GetDouble();
                        foreach (var ele in elements)
                        {
                            yieldRate = yieldRate.GetYeildRate(baseMaterialElementes, item.MaterialId, ele.ElementId);
                            var eleWeight = (ele.Content * item.RequiredWeight / 100) * yieldRate;
                            var element = tempList.FirstOrDefault(c => c.ElementId == ele.ElementId);
                            if (element == null)
                            {
                                tempList.Add(new ElementWeight
                                {
                                    ElementId = ele.ElementId,
                                    Weight = eleWeight
                                });
                            }
                            else
                                element.Weight += eleWeight;
                        }
                    }
                    #endregion

                    return tempList;
                }

                //3.0 合并多次计算结果   0=正常 1=继续计算 2=重试
                int MergeResult(List<MaterialAmountBOM> oldResult)
                {
                    if (removeStock)
                    {
                        removeStock = false;
                        var removed = false;
                        foreach (var item in oldResult)
                        {
                            var tempStock = 0d;
                            var materialStock = stock.Where(c => c.MaterialId == item.MaterialId && c.State >= 0).ToList();
                            foreach (var s in materialStock.OrderByDescending(c => c.MaterialWeight))
                            {
                                var materialWeight = s.MaterialWeight.GetDouble();
                                if (materialWeight > item.RequiredWeight || (tempStock + materialWeight) > item.RequiredWeight)
                                {
                                    stock.Remove(s);
                                    removed = true;
                                    continue;
                                }
                                tempStock += materialWeight;
                            }
                        }
                        if (removed)
                        {
                            stockGroup = stock.GroupBy(c => c.MaterialId);
                            return 2;
                        }
                    }
                    var rate = 1d;
                    foreach (var item in oldResult)
                    {
                        var value = item.RequiredWeight;
                        var mbStock = stock.FirstOrDefault(x => x.BoxNo == item.MaterialBoxNO);
                        var materialStock = mbStock.MaterialWeight.GetDouble();
                        var usedStock = usedBatchMaterials.FirstOrDefault(x => x.MaterialBoxNO == item.MaterialBoxNO)?.RequiredWeight ?? 0;

                        //配料台，和配料备料台，默认足够
                        if (mbStock.State < 0)
                            continue;

                        if (value >= materialStock - usedStock)
                        {
                            var tmpRate = value / (materialStock - usedStock);
                            if (tmpRate > rate)
                                rate = tmpRate;
                        }
                        else
                        {
                            stock.Remove(mbStock);
                            stockGroup = stock.GroupBy(c => c.MaterialId);
                            return 2;
                        }
                    }
                    foreach (var item in oldResult)
                    {
                        var used = usedBatchMaterials.FirstOrDefault(x => x.MaterialBoxNO == item.MaterialBoxNO);
                        if (used == null)
                        {
                            used = new MaterialAmountBOM()
                            {
                                MaterialId = item.MaterialId,
                                BatchNO = item.BatchNO,
                                RequiredWeight = item.RequiredWeight.GetStock(rate),
                                MaterialName = item.MaterialName,
                                CurrentRequiredWeight = item.RequiredWeight,
                                MaterialBoxNO = item.MaterialBoxNO
                            };
                            usedBatchMaterials.Add(used);
                        }
                        else
                        {
                            used.RequiredWeight += item.RequiredWeight.GetStock(rate);
                        }
                        var mbStock = stock.FirstOrDefault(x => x.BoxNo == item.MaterialBoxNO);
                        var materialStock = mbStock.MaterialWeight.GetDouble();
                        if (used.RequiredWeight.CheckStock(materialStock))
                        {
                            used.RequiredWeight = materialStock;
                        }
                        if (rate <= 1)
                            used.RequiredWeight = used.RequiredWeight.ToString("F2").GetDouble();
                    }
                    return rate <= 1 ? 0 : 1;
                }

                //4.0 匹配相应的库存批号元素占比
                (bool, string, List<ElementRate>) GetMsaterialElementes(List<ElementRate> temp)
                {
                    foreach (var materialStock in stockGroup)
                    {
                        foreach (var item in materialStock.OrderByDescending(x => x.State).ThenByDescending(x => x.MaterialWeight))
                        {
                            //1.0 获取当前批号物料 已使用的库存
                            var tempStock = usedBatchMaterials.FirstOrDefault(x => x.MaterialBoxNO == item.BoxNo);
                            if (tempStock != null)
                            {
                                //1.1 如果库存足够，不需要换批次
                                if (item.MaterialWeight.GetDouble() > tempStock.RequiredWeight)
                                    break;
                                else
                                {
                                    //1.2 更换批次
                                    var usedBatch = temp.Where(x => x.BoxNo == item.BoxNo).ToList();
                                    for (int i = 0; i < usedBatch.Count; i++)
                                    {
                                        temp.Remove(usedBatch[i]);
                                    }
                                    continue;
                                }
                            }
                            var list = msaterialCheckList.Where(c => c.MaterialId == item.MaterialId && item.BatchNumber == c.BatchNumber).ToList();
                            if (list.Any())
                            {
                                list.ForEach(c =>
                                {
                                    c.BoxNo = item.BoxNo;
                                });
                                temp.AddRange(list);
                                if (!allMaterialElementes.Any(x => x.MaterialId == item.MaterialId && x.BatchNumber == item.BatchNumber))
                                    allMaterialElementes.AddRange(list);
                                break;
                            }
                            list = baseMaterialElementes.Where(c => c.MaterialId == item.MaterialId).Select(c => new ElementRate
                            {
                                MaterialId = c.MaterialId,
                                BatchNumber = item.BatchNumber,
                                ElementId = c.ElementId,
                                Content = c.Content.GetDouble(),
                                Check = false,
                                BoxNo = item.BoxNo
                            }).ToList();
                            if (list.Any())
                            {
                                temp.AddRange(list);
                                if (!allMaterialElementes.Any(x => x.MaterialId == item.MaterialId && x.BatchNumber == item.BatchNumber))
                                    allMaterialElementes.AddRange(list);
                                break;
                            }
                        }
                    }
                    var notExistsMaterial = productDetails.FirstOrDefault(c => !temp.Any(x => x.MaterialId == c.MaterialsId));

                    if (notExistsMaterial != null)
                    {
                        var first = usedBatchMaterials.First(c => c.MaterialId == notExistsMaterial.MaterialsId);
                        var notExistsMaterialStock = stock.Where(c => c.MaterialId == notExistsMaterial.MaterialsId).Sum(c => c.MaterialWeight);
                        return (false, $"【{notExistsMaterial.MaterialsName}】库存不足,当前库存【{notExistsMaterialStock}】,所需库存预估值【{first.CurrentRequiredWeight}】！", null);
                    }
                    return (true, "", temp);
                }

                //5.0 检测元素占比
                (bool, string) CheckResult()
                {
                    var list = GetElementWeight();
                    foreach (var item in elementBOMList.Where(c => c.ElementId != mainMaterial.ElementId))
                    {
                        var limit = GetLimit(item);
                        var element = list.First(c => c.ElementId == item.ElementId);
                        var elementRate = (element.Weight / tempRequiredWeight * 100).ToString("F6").GetDouble();
                        if (elementRate > limit.Item1 || elementRate < limit.Item2)
                        {
                            return (false, $"元素【{item.ElementName}】占比【{elementRate}】，超出限定范围【{limit.Item2}-{limit.Item1}】！");
                        }
                    }

                    return (true, "");
                }

                //6.0 换品残留计算
                async Task<(bool, string)> ResidualCalculation()
                {
                    //6.1 获取上炉生产情况
                    lastTimeInductionFurnacePD = repository.DbContext.Set<InductionFurnacePD>().Where(c => c.HeatNo == inductionFurnacePD.HeatNo && c.Id != inductionFurnacePD.Id).OrderByDescending(c => c.CreateDate).FirstOrDefault();

                    if (lastTimeInductionFurnacePD == null || lastTimeInductionFurnacePD.OrderFormNo == inductionFurnacePD.OrderFormNo)
                        return (true, "");

                    //6.2 获取感应炉残存量
                    var base_InductionFurnace = await repository.FindAsyncFirst<Base_InductionFurnace>(c => c.HeatNo == inductionFurnacePD.HeatNo);
                    var residualSettings = base_InductionFurnace.ResidualSettings.GetDouble();
                    //6.3 换品残存参与计算
                    if (lastTimeInductionFurnacePD.ProductName == inductionFurnacePD.ProductName)
                        return (true, "");
                    var residualCheck = repository.DbContext.Set<CheckList>().Where(c => c.MaterialNo == lastTimeInductionFurnacePD.ProductName && c.BatchNo == lastTimeInductionFurnacePD.BatchNumber).OrderByDescending(c => c.GetTime).FirstOrDefault();
                    if (residualCheck == null) return (false, $"未获取到该产品【{lastTimeInductionFurnacePD.ProductName}_{lastTimeInductionFurnacePD.BatchNumber}】的检化验信息！");
                    var residualElements = await repository.FindAsync<CheckListDetailed>(c => c.CheckListId == residualCheck.Id);

                    var residualWeight = (lastTimeInductionFurnacePD.Output.GetDouble() * residualSettings / 100);
                    foreach (var item in elementBOMList)
                    {
                        var residualElement = residualElements.FirstOrDefault(c => c.ElementId == item.ElementId);
                        if (residualElement == null)
                            continue;
                        var elementWeight = residualWeight * residualElement.Result.GetDouble() / 100;
                        residualMaterialElements.Add(new ElementWeight()
                        {
                            ElementId = item.ElementId,
                            Weight = elementWeight,
                            BatchNumber = lastTimeInductionFurnacePD.BatchNumber,
                            MaterialId = lastTimeInductionFurnacePD.ProductName
                        });
                        residualElementTotalWeight += elementWeight;
                    }
                    return (true, "");
                }

                //7.0 返回料计算
                async Task<(bool, string)> ReturnedMaterialCalculation(ReturnedMaterial returnedMaterial)
                {
                    //6.1 返回料计算
                    if (!returnedMaterial.Weight.HasValue || returnedMaterial.Weight <= 0)
                        return (true, "");
                    var returnMaterialCheck = repository.DbContext.Set<CheckList>().Where(c => c.MaterialNo == returnedMaterial.ProductNo && c.BatchNo == returnedMaterial.Category).OrderByDescending(c => c.GetTime).FirstOrDefault();
                    if (returnMaterialCheck == null) return (false, $"未获取到该产品【{returnedMaterial.ProductNo}_{returnedMaterial.Category}】的检化验信息！");
                    var returnMaterialCheckElements = await repository.FindAsync<CheckListDetailed>(c => c.CheckListId == returnMaterialCheck.Id);

                    var residualWeight = returnedMaterial.Weight.Value.GetDouble();
                    foreach (var item in elementBOMList)
                    {
                        var residualElement = returnMaterialCheckElements.FirstOrDefault(c => c.ElementId == item.ElementId);
                        if (residualElement == null)
                            continue;
                        var elementWeight = residualWeight * residualElement.Result.GetDouble() / 100;
                        returnMaterialElements.Add(new ElementWeight()
                        {
                            ElementId = item.ElementId,
                            Weight = elementWeight,
                            MaterialId = returnedMaterial.ProductNo,
                            BatchNumber = returnedMaterial.Category
                        });
                        returnMaterialRealWeight += elementWeight;
                    }
                    return (true, "");
                }

                //8.0 生成配料单
                (bool, string) CreateIngredientList()
                {
                    var nowDate = DateTime.Now;
                    var no = repository.DbContext.Set<IngredientList>().Count(c => c.CreateDate >= nowDate.Date && c.CreateDate < nowDate.Date.AddDays(1)) + 1;
                    var batchNo = $"P{nowDate.ToString("yyyyMMdd")}#{inductionFurnacePD.HeatNo}#{no.ToString().PadLeft(6, '0')}";
                    var id = Guid.NewGuid().ToString();
                    //8.1 生成主表
                    var materialRequisition = new IngredientList()
                    {
                        Id = id,
                        HeatNo = inductionFurnacePD.HeatNo,
                        OrderNumber = inductionFurnacePD.OrderFormNo,
                        IFPDId = inductionFurnacePD.Id,
                        CreateDate = nowDate,
                        DeletionFlag = 0,
                        BatchingNo = batchNo,
                        PlannedFeedingQuantity = inductionFurnacePD.RequiredWeight,
                        Heat = inductionFurnacePD.Heat

                    };
                    //8.2 设置主表默认字段
                    materialRequisition.SetCreateDefaultVal();

                    var notice = new NotificationForm
                    {
                        Content = $"订单号【{inductionFurnacePD.OrderFormNo}】已配料，请前住【配料单查询】查看",
                        DeletionFlag = 0,
                        OrderNumber = batchNo,
                        OrderType = "3",
                        RedirectPath = "",
                        SourceOrderNumber = inductionFurnacePD.OrderFormNo
                    };
                    notice.SetCreateDefaultVal();

                    var materialElementRates = new List<IngredientListElement>();

                    //8.3 生成料箱绑定
                    var batchingMaterialBoxes = new List<BatchingMaterialBox>();

                    //8.3 生成明细
                    var list = usedBatchMaterials.Select(c =>
                    {
                        var box = new BatchingMaterialBox()
                        {
                            MaterialBoxNo = c.MaterialBoxNO,
                            Id = Guid.NewGuid().ToString(),
                            OrderNumber = id,
                            Types = 1
                        };
                        batchingMaterialBoxes.Add(box);
                        var boxStock = stock.First(x => x.BoxNo == c.MaterialBoxNO);
                        var model = new IngredientListDetails()
                        {
                            MaterialId = c.MaterialId,
                            MaterialName = c.MaterialName,
                            RequiredWeight = c.RequiredWeight.ToString(),
                            Id = Guid.NewGuid().ToString(),
                            IngredientListId = id,
                            BatchNumber = c.BatchNO,
                            State = 0,
                            IngredientsType = productDetails.First(x => x.MaterialsId == c.MaterialId).Category,
                            MaterialBoxNo = c.MaterialBoxNO,
                            Source = boxStock.State >= 0 ? "1" : (boxStock.State == -1 ? "2" : "3")
                        };
                        materialElementRates.AddRange(GetMaterialElementRates(model));
                        return model;
                    }).ToList();

                    //8.4 记录返回料
                    if (returnMaterial != null)
                    {
                        var returnMaterialDetailId = Guid.NewGuid().ToString();
                        list.Add(new IngredientListDetails()
                        {
                            MaterialId = returnMaterial.ProductNo,
                            MaterialName = returnMaterial.Name,
                            RequiredWeight = returnMaterial.Weight.ToString(),
                            Id = returnMaterialDetailId,
                            IngredientListId = id,
                            BatchNumber = returnMaterial.Category,
                            State = 0,
                            IngredientsType = 2 //返回料
                        });
                        materialElementRates.AddRange(returnMaterialElements.Select(c => new IngredientListElement
                        {
                            IngredientListDetailsId = returnMaterialDetailId,
                            IngredientListId = id,
                            ActualProportion = (c.Weight / returnMaterialRealWeight).ToDecimal(),
                            ActualWeight = c.Weight.ToDecimal(),
                            BatchNumber = returnMaterial.Category,
                            MaterialId = returnMaterial.ProductNo,
                            ElementId = c.ElementId,
                            ElementName = baseElements.First(x => x.Id == c.ElementId).ElementName,
                            Id = Guid.NewGuid().ToString(),
                            MaterialName = returnMaterial.Name,
                            TargetWeight = c.Weight.ToDecimal()
                        }).ToList());
                    }

                    //8.5 记录换品残留
                    if (lastTimeInductionFurnacePD != null && residualElementTotalWeight > 0)
                    {
                        list.Add(new IngredientListDetails()
                        {
                            MaterialId = lastTimeInductionFurnacePD.ProductName,
                            MaterialName = lastTimeInductionFurnacePD.ProductName,
                            RequiredWeight = residualElementTotalWeight.ToString(),
                            Id = Guid.NewGuid().ToString(),
                            IngredientListId = id,
                            BatchNumber = lastTimeInductionFurnacePD.BatchNumber,
                            State = 0,
                            IngredientsType = 3 //残留
                        });
                    }

                    lock (lockIngredientLis)
                    {
                        //8.5 获取已生成配料单
                        lastBill = repository.DbContext.Set<IngredientList>().FirstOrDefault(c => c.IFPDId == inductionFurnacePD.Id && c.DeletionFlag != 1);
                        if (lastBill != null && lastBill.State > 0)
                        {
                            return (false, $"该生产计划生成的配料单【{lastBill.BatchingNo}】已处理，请勿重复操作！");
                        }
                        var response = new WebResponseContent();
                        response = repository.DbContextBeginTransaction(() =>
                        {
                            if (lastBill != null)
                            {
                                //7.6 删除配料单
                                lastBill.DeletionFlag = 1;
                                lastBill.SetModifyDefaultVal();
                                var type = typeof(IngredientList);
                                var listField = type.GetDeletionFileds();
                                //软删除已生成配料单
                                repository.Update(lastBill, listField.ToArray());
                                repository.DapperContext.ExcuteNonQuery("DELETE dbo.IngredientListElement  WHERE IngredientListId = @IngredientListId;DELETE dbo.BatchingMaterialBox WHERE OrderNumber = @IngredientListId AND Types = 1;" +
                                    "UPDATE NotificationForm SET DeletionFlag = 1 WHERE OrderNumber = @OrderNumer  AND OrderType = 3",
                                    new { IngredientListId = lastBill.Id, OrderNumer = lastBill.BatchingNo });
                            }

                            //更新返回料占用
                            if (returnMaterial != null)
                            {
                                returnMaterial.Heat = inductionFurnacePD.Heat;
                                returnMaterial.SetModifyDefaultVal();
                                repository.DbContext.Update(returnMaterial);
                            }
                            //生成新的配料单
                            repository.DbContext.Entry(materialRequisition).State = EntityState.Added;
                            repository.DbContext.Entry(notice).State = EntityState.Added;
                            list.ForEach(e =>
                            {
                                e.SetCreateDefaultVal();
                                repository.DbContext.Entry(e).State = EntityState.Added;
                            });
                            materialElementRates.ForEach(c =>
                            {
                                repository.DbContext.Entry(c).State = EntityState.Added;
                            });
                            batchingMaterialBoxes.ForEach(e =>
                            {
                                e.SetCreateDefaultVal();
                                repository.DbContext.Entry(e).State = EntityState.Added;
                            });
                            repository.DbContext.SaveChanges();
                            response.OK(ResponseType.SaveSuccess);
                            return response;
                        });
                        if (response.Status)
                            sendNoticeAction?.Invoke(notice);
                        return (response.Status, response.Message);
                    }
                }

                //9.0 计算配料明细元素占比
                List<IngredientListElement> GetMaterialElementRates(IngredientListDetails detail)
                {
                    var elementRateList = new List<IngredientListElement>();
                    foreach (var item in usedBatchMaterials.Where(c => c.MaterialBoxNO == detail.MaterialBoxNo))
                    {
                        var elements = allMaterialElementes.Where(c => c.MaterialId == item.MaterialId && item.BatchNO == c.BatchNumber);
                        var material = productDetails.First(c => c.MaterialsId == item.MaterialId);
                        var yieldRate = ((material.YieldRate.HasValue ? material.YieldRate.Value : 100) / 100).GetDouble();
                        foreach (var ele in elements)
                        {
                            yieldRate = yieldRate.GetYeildRate(baseMaterialElementes, item.MaterialId, ele.ElementId);
                            var eleWeight = (ele.Content * item.RequiredWeight / 100) * yieldRate;
                            var elementRate = (eleWeight / tempRequiredWeight * 100).ToString("F6").GetDouble();
                            var element = elementBOMList.First(c => c.ElementId == ele.ElementId);
                            elementRateList.Add(new IngredientListElement
                            {
                                ElementId = ele.ElementId,
                                ElementContent = ele.Content.ToDecimal(),
                                ActualProportion = elementRate.ToDecimal(),
                                ElementName = element.ElementName,
                                ActualWeight = eleWeight.ToDecimal(),
                                Id = Guid.NewGuid().ToString(),
                                BatchNumber = item.BatchNO,
                                MaterialName = item.MaterialName,
                                MaterialId = item.MaterialId,
                                IngredientListDetailsId = detail.Id,
                                IngredientListId = detail.IngredientListId,
                                TargetProportion = GetElementRate(element).ToDecimal()
                            });
                        }
                    }

                    return elementRateList;
                }
                #endregion

            }
            catch (Exception ex)
            {
                return (false, ex.Message, null);
            }
            finally
            {
                IsCanExcute = true;
            }
        }

        private static object _canExcuteObject = new object();
        /// <summary>
        /// 加锁，单个任务执行
        /// </summary>
        /// <returns></returns>
        private static bool CanExcute()
        {
            if (IsCanExcute)
            {
                lock (_canExcuteObject)
                {
                    if (IsCanExcute)
                    {
                        IsCanExcute = false;
                        return true;
                    }
                }
            }
            return false;
        }

        public static double GetYeildRate(this double materialYeildRate, List<MaterialElement> elements, string materialId, string elementId, bool isReplenishmentProcessing = false)
        {
            if (!AppSetting.EnableMaterialElementYieldRate)
                return materialYeildRate;
            var element = elements.FirstOrDefault(c => c.MaterialId == materialId && c.ElementId == elementId);
            if (element == null) return 1;
            decimal rate = 100;
            if (isReplenishmentProcessing && element.ReplenishmentYieldRate.HasValue)
                rate = element.ReplenishmentYieldRate.Value;
            if (!isReplenishmentProcessing && element.YieldRate.HasValue)
                rate = element.YieldRate.Value;
            return (rate / 100).GetDouble();
        }
        /// <summary>
        /// 矩阵计算
        /// </summary>
        /// <param name="msaterialElementes">物料元素占比清单</param>
        /// <param name="elementBOMList">产品元素占比清单</param>
        /// <param name="productDetails">产品物料明细</param>
        /// <param name="requiredWeight">需求重量</param>
        /// <param name="mainComponents">元素实际占比</param>
        /// <returns></returns>
        private static (bool, string, List<MaterialAmountBOM>) Computed(List<ElementRate> msaterialElementes, List<ProductElementBOM> elementBOMList, List<ProductMaterials> productDetails, double requiredWeight, List<ElementRate> mainComponents, List<MaterialElement> baseMaterialElementes)
        {
            var data = new double[productDetails.Count, productDetails.Count];
            var resultData = new Matrix(productDetails.Count, 1);
            var total = requiredWeight;
            for (int i = 0; i < mainComponents.Count; i++)
            {
                var element = mainComponents[i];
                var tmpBom = elementBOMList.First(c => c.ElementId == element.ElementId);
                var elementRate = tmpBom.TargetProportion.GetDouble();
                var elementLimit = GetLimit(tmpBom);
                var elementAmount = elementRate * total;
                resultData[i, 0] = elementAmount;
                for (int j = 0; j < productDetails.Count; j++)
                {
                    var material = productDetails[j];
                    var yieldRate = material.YieldRate.GetDouble() != 0 ? material.YieldRate.GetDouble() / 100 : 1;
                    yieldRate = yieldRate.GetYeildRate(baseMaterialElementes, material.MaterialsId, element.ElementId);
                    var materialElement = msaterialElementes.FirstOrDefault(c => c.MaterialId == material.MaterialsId && c.ElementId == element.ElementId);
                    if (materialElement == null)
                    {
                        data[i, j] = 0;
                        continue;
                    }
                    var tmpRate = materialElement.Content.GetDouble() * yieldRate;
                    data[i, j] = tmpRate;
                }
            }
            var sourceData = new Matrix(data);
            var result = sourceData.Inverse() * resultData;

            var list = new List<MaterialAmountBOM>();
            for (int i = 0; i < result.Rows; i++)
            {
                var material = productDetails[i];
                var stock = msaterialElementes.First(c => c.MaterialId == material.MaterialsId);
                var detial = new MaterialAmountBOM
                {
                    MaterialId = material.MaterialsId,
                    MaterialName = material.MaterialsName,
                    RequiredWeight = result[i, 0].ToString("F2").GetDouble(),
                    BatchNO = stock.BatchNumber,
                    MaterialBoxNO = stock.BoxNo
                };
                list.Add(detial);
            }

            return (true, "", list);
        }

        #endregion

        #region 辅助函数
        public static double GetStock(this double stock, double rate)
        {
            if (rate <= 1)
                return stock;
            return (stock / rate).ToString("F2").GetDouble();
        }

        private static bool CheckStock(this double usedStock, double stock)
        {
            usedStock = usedStock.ToString("F2").GetDouble();
            stock = stock.ToString("F2").GetDouble();
            return usedStock >= stock - 0.01 && usedStock <= stock + 0.01;
        }
        /// <summary>
        /// 粗略计算可以取整
        /// </summary>
        /// <param name="weight"></param>
        /// <param name="total"></param>
        /// <returns></returns>
        private static double GetWeight(double weight, double total)
        {
            var rate = weight / total;
            if (rate < 0.01)
            {
                return Math.Round(weight, 0);
            }
            else
            {
                var divisor = 1;

                if (rate > 0.5)
                    divisor = 10;
                else if (rate > 0.3)
                    divisor = 5;
                else if (rate > 0.1)
                    divisor = 3;

                var count = (int)Math.Floor(weight / divisor);
                if (weight % divisor > 0)
                    count++;
                weight = count * divisor;
            }
            return weight;
        }

        /// <summary>
        /// 获取元素成分占比
        /// </summary>
        /// <param name="element"></param>
        /// <param name="isLowerComputed">是否按最低内控标准计算，当微量元素超出的时候，含该元素比例较高的按最低内控标准计算</param>
        /// <param name="isFirst">是否为首次计算，首次计算按目标值计算</param>
        /// <returns></returns>
        private static double GetElementRate(ProductElementBOM element, bool isLowerComputed = false, bool isFirst = true)
        {
            double elementRate = 0d;
            if (element.TargetProportion.HasValue)
                elementRate = element.TargetProportion.Value.GetDouble();
            else if (element.UpperLimitIC.HasValue)
            {
                var lowerLimit = 0d;
                if (element.LowerLimitStandard.HasValue)
                    lowerLimit = element.LowerLimitIC.Value.GetDouble();
                elementRate = (element.UpperLimitIC.Value.GetDouble() - lowerLimit) / 2 + lowerLimit;
            }
            else if (element.UpperLimitStandard.HasValue)
            {
                var lowerLimit = 0d;
                if (element.LowerLimitStandard.HasValue)
                    lowerLimit = element.LowerLimitStandard.Value.GetDouble();
                elementRate = (element.UpperLimitStandard.Value.GetDouble() - lowerLimit) / 2 + lowerLimit;
            }
            if (!isFirst)
            {
                if (isLowerComputed)
                {
                    double lowerLimit = 0;
                    if (element.LowerLimitIC.HasValue)
                        lowerLimit = element.LowerLimitIC.Value.GetDouble();
                    else if (element.LowerLimitStandard.HasValue)
                        lowerLimit = element.LowerLimitStandard.Value.GetDouble();
                    elementRate = (elementRate - lowerLimit) / 10 + lowerLimit;
                }
            }
            return elementRate;
        }


        /// <summary>
        /// 获取内控范围
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        public static (double, double) GetLimit(ProductElementBOM element)
        {
            if (element.UpperLimitIC.HasValue)
            {
                var lowerLimit = 0d;
                if (element.LowerLimitStandard.HasValue)
                    lowerLimit = element.LowerLimitIC.Value.GetDouble();
                return (element.UpperLimitIC.Value.GetDouble(), lowerLimit);
            }
            if (element.UpperLimitStandard.HasValue)
            {
                var lowerLimit = 0d;
                if (element.LowerLimitStandard.HasValue)
                    lowerLimit = element.LowerLimitStandard.Value.GetDouble();
                return (element.UpperLimitStandard.Value.GetDouble(), lowerLimit);
            }
            return (0, 0);
        }

        #endregion

        #region 补料计算

        class LimitMaterialElement
        {
            public string MaterialId { get; set; }

            public string MaterialName { get; set; }
            public string ElementId { get; set; }

            public double Current { get; set; }

            public double Target { get; set; }

            /// <summary>
            /// 目标值
            /// </summary>
            public double Diff { get; set; }


            /// <summary>
            /// 是否优先考虑
            /// </summary>
            public bool First { get; set; }
        }

        private static object ReplenishmentListLock = new object();
        public static async Task<(bool, string, List<MaterialAmountBOM>)> ReplenishmentListCalculation<T>(this string id, IRepository<T> repository, Func<NotificationForm, Task> sendNoticeAction) where T : BaseEntity
        {
            if (!CanExcute())
            {
                return (false, "计算进行中，请勿重复操作.！", null);
            }
            try
            {
                var ifr = await repository.FindAsyncFirst<InductionFurnaceReplenishment>(c => c.Id == id);
                if (ifr == null)
                    return (false, "补料数据不存在！", null);

                //1.1 获取产品名称
                var productName = ifr.ProductName;
                //1.2 获取生产计划需求重量
                double requiredWeight = ifr.MaterialWeight.GetDouble();

                //2.0 获取产品主数据
                var product = await repository.FindAsyncFirst<ProductData>(c => c.Name == productName);
                if (product == null) return new(false, "该产品信息未配置，获取产品信息失败！", null);

                var materialQueryable = repository.DbContext.Set<MaterialMaster>().Where(c => !c.DeletionFlag.HasValue || c.DeletionFlag == 0);
                //2.1 获取产品明细物料数据
                var productDetailsQueryable = repository.DbContext.Set<ProductMaterials>().Where(c => c.ProductId == product.Id && (!c.DeletionFlag.HasValue || c.DeletionFlag == 0)).Join(materialQueryable, t => t.MaterialsId, s => s.Id, (t, s) => new
                {
                    t.MaterialsId,
                    t.MaterialsName,
                    t.ProductId,
                    t.YieldRate,
                    s.Category
                });
                var productDetails = productDetailsQueryable.ToList();
                //var productDetails = await repository.FindAsync<ProductMaterials>(c => c.ProductId == product.Id && (!c.DeletionFlag.HasValue || c.DeletionFlag == 0));
                //2.2 判断物料信息数据
                if (!productDetails.Any())
                    return new(false, "该产品物料清单信息未配置！", null);

                var ifpd = await repository.FindAsyncFirst<InductionFurnacePD>(c => c.ProductName == productName && c.BatchNumber == ifr.BatchNumber);
                if (ifpd == null)
                    return new(false, "未找到相关生产数据！", null);

                var lastBill = await repository.FindAsyncFirst<ReplenishmentList>(c => c.IFPDId == ifpd.Id && c.DeletionFlag != 1);
                if (lastBill != null && lastBill.Enable > 0)
                    return (false, $"该生产计划生成的配料单【{lastBill.BatchingNo}】已处理，请勿重复操作！", null);

                //4.0 获取元素基础数据
                var baseElements = await repository.FindAsync<BaseElement>(c => c.Enable == 1 || !c.Enable.HasValue);

                var baseMaterialElementes = await repository.FindAsync<MaterialElement>(c => productDetails.Select(x => x.MaterialsId).Contains(c.MaterialId));
                //6.0 获取产品 元素控制数据
                var elementBOMList = await repository.FindAsync<ProductElementBOM>(c => c.ProductId == product.Id && (!c.DeletionFlag.HasValue || c.DeletionFlag == 0));
                if (!elementBOMList.Any())
                    return new(false, "该产品产品元素清单信息未配置！", null);
                //6.1 设置组合元素的计算元素
                else if (elementBOMList.Any(c => !string.IsNullOrEmpty(c.CalculatingElement)))
                {
                    foreach (var item in elementBOMList.Where(c => !string.IsNullOrEmpty(c.CalculatingElement)))
                    {
                        var baseElement = baseElements.FirstOrDefault(c => c.ElementSymbols.Trim().ToLower() == item.CalculatingElement.Trim().ToLower());
                        if (baseElement == null)
                            return (false, $"未找到该计算元素【{item.CalculatingElement}】的基础元素配置信息！", null);
                        item.ElementId = baseElement.Id;
                    }
                }

                var checkList = repository.DbContext.Set<CheckList>().FirstOrDefault(c => c.Category == 0 && c.MaterialNo == product.MaterialNo && ifr.BatchNumber == c.BatchNo && c.Step == 0);
                if (checkList == null)
                    return (false, "未查询相关检化验信息！", null);


                //8.1 获取物料主元素信息
                var mainComponents = baseMaterialElementes.GroupBy(c => c.MaterialId).Select(c => c.ToList().OrderByDescending(x => x.Content).FirstOrDefault()).ToList();
                //8.2 不可检出项
                var elementBOMs = elementBOMList.Where(c => !mainComponents.Any(x => x.ElementId == c.ElementId)).ToList();

                var checkListDetails = repository.DbContext.Set<CheckListDetailed>().Where(c => c.CheckListId == checkList.Id).ToList();
                var mainMaterialList = elementBOMList.Join(baseMaterialElementes,
                s => s.ElementId, t => t.ElementId, (s, t) => new
                {
                    t.MaterialId,
                    s.TargetProportion,
                    t.Content,
                    t.ElementId
                }).OrderByDescending(c => c.TargetProportion).ThenByDescending(c => c.Content);

                var mainMaterial = mainMaterialList.FirstOrDefault();
                var addMaterialList = new List<MaterialAmountBOM>();
                //需要补料的元素
                var list = new List<LimitMaterialElement>();
                var elementsWeight = new List<ElementWeight>();
                var addMaterialElementsRate = new List<ElementRate>();

                var tempMainComponents = mainComponents.Where(c => c.MaterialId != mainMaterial.MaterialId);
                foreach (var item in tempMainComponents)
                {
                    var element = checkListDetails.FirstOrDefault(c => c.ElementId == item.ElementId);
                    var bom = elementBOMList.First(c => c.ElementId == item.ElementId);

                    var limit = GetLimit(bom);
                    var limitResult = element.Result.GetDouble();
                    elementsWeight.Add(new ElementWeight
                    {
                        ElementId = element.ElementId,
                        MaterialId = item.MaterialId,
                        Weight = requiredWeight * limitResult / 100
                    });
                    if (limitResult < limit.Item2)
                    {
                        var target = GetElementRate(bom, 1);
                        list.Add(new LimitMaterialElement
                        {
                            MaterialId = item.MaterialId,
                            ElementId = item.ElementId,
                            Current = limitResult,
                            Target = target,
                            Diff = target - limitResult
                        });
                    }
                    else if (limitResult > limit.Item1)
                    {
                        var target = GetElementRate(bom, 2);
                        list.Add(new LimitMaterialElement
                        {
                            MaterialId = item.MaterialId,
                            ElementId = item.ElementId,
                            Current = limitResult,
                            Target = target,
                            Diff = limitResult - target,
                            First = true
                        });

                    }
                }

                if (!list.Any())
                    return (false, "没有需要补料的数据！", null);

                var lastBillStcokMaterialBox = new List<string>();

                var sQueryable = repository.DbContext.Set<MaterialBox>().Where(c => c.OrderNumber == ifr.OrderFormNo && (c.State == 10 || c.State == 99));
                if (Sys_ConfigKeys.LockStock.GetConfig() == "1")
                {
                    var lastBillId = lastBill?.Id ?? "";
                    lastBillStcokMaterialBox = repository.DbContext.Set<BatchingMaterialBox>().Join(sQueryable, t => t.MaterialBoxNo, s => s.BoxNo, (t, s) => new { t.MaterialBoxNo, t.OrderNumber, t.Types }).Where(c => c.OrderNumber != lastBillId).Select(c => c.MaterialBoxNo).ToList();
                }

                //3.0 获取计划单 物料库存数据
                var stockQueryable = sQueryable.Select(c => new
                {
                    c.BatchNumber,
                    c.MaterialId,
                    Stock = c.MaterialWeight,
                    State = c.State ?? 0,
                    MaterialBoxNo = c.BoxNo
                });
                var stock = stockQueryable.ToList();
                foreach (var item in lastBillStcokMaterialBox)
                {
                    var tmpStock = stock?.FirstOrDefault(c => c.MaterialBoxNo == item);
                    if (tmpStock != null)
                        stock.Remove(tmpStock);
                }
                //if (!stock.Any())
                //    return new(false, "计划单物料库存数据异常！", null);
                //3.1 判断是否存在无库存的物料
                //var stockGroup = stock.GroupBy(c => c.MaterialId);
                //var notExistsMaterial = stockGroup.Any(c => c.Key == mainMaterial.MaterialId);
                //if (!notExistsMaterial)
                //    return new(false, $"缺少【{productDetails.First(c => c.MaterialsId == mainMaterial.MaterialId).MaterialsName}】在库库存信息！", null);


                //配料区库存 和 配料备料区库存
                var ingredientsAreaStockQueryable = repository.DbContext.Set<IngredientsArea>().Join(productDetailsQueryable, t => t.BatchingMaterials, s => s.MaterialsId, (t, s) => new
                {
                    s.MaterialsId,
                    s.MaterialsName,
                    t.BatchingBatch,
                    t.BatchingRemainingWeight,
                    t.MaterialPreparationBatch,
                    t.MaterialPreparationWeight,
                    t.MaterialPreparationMaterial,
                    t.BatchingMaterial
                });

                var checkListQeryable = stockQueryable.Select(c => new { c.MaterialId, c.BatchNumber })
                    .Union(ingredientsAreaStockQueryable.Select(c => new { MaterialId = c.MaterialsId, BatchNumber = c.BatchingBatch }))
                    .Union(ingredientsAreaStockQueryable.Select(c => new { MaterialId = c.MaterialsId, BatchNumber = c.MaterialPreparationBatch }))
                    .GroupBy(c => new { c.MaterialId, c.BatchNumber })
                    .Select(c => new { c.Key.MaterialId, c.Key.BatchNumber });

                var ingredientsAreaStock = ingredientsAreaStockQueryable.ToList();
                //5.0 获取物料同批次检化验单号
                var queryableList = repository.DbContext.Set<CheckList>().Where(c => c.Category == 1).Join(checkListQeryable, s => new { s.MaterialId, BatchNumber = s.BatchNo }, t => new { t.MaterialId, t.BatchNumber }, (s, t) => new
                {
                    t.BatchNumber,
                    t.MaterialId,
                    s.Id,
                    s.GetTime
                }
                ).ToList();

                //5.1 同批次物料检化验结果取最后一次
                var materialCheckList = queryableList.GroupBy(c => new { c.MaterialId, c.BatchNumber }).Select(c => new
                {
                    c.Key.MaterialId,
                    c.Key.BatchNumber,
                    c.OrderByDescending(x => x.GetTime).First().Id
                }).ToList();
                //5.2 获取检测单明细
                var materialCheckListDetaild = await repository.FindAsync<CheckListDetailed>(c => materialCheckList.Select(x => x.Id).Contains(c.CheckListId));
                //5.3 关联获取物料批次检化验结果 元素成分占比
                var msaterialCheckList = materialCheckListDetaild.Join(materialCheckList, s => s.CheckListId, t => t.Id, (s, t) => new ElementRate
                {
                    MaterialId = t.MaterialId,
                    BatchNumber = t.BatchNumber,
                    ElementId = s.ElementId,
                    Check = true,
                    Content = s.Result.GetDouble()
                }).ToList();

                var currentMainMaterialElementRate = checkListDetails.First(c => c.ElementId == mainMaterial.ElementId);
                var baseInductionFurnace = await repository.FindAsyncFirst<Base_InductionFurnace>(c => c.Id == ifr.HeatId);
                Retry:
                var tempRequiredWeight = requiredWeight + addMaterialList.Sum(c => c.RequiredWeight);
                foreach (var item in list.OrderByDescending(c => c.First).ThenByDescending(c => c.Diff).ToList())
                {
                    var weight = 0d;
                    var content = 0d;
                    var imaterial = ingredientsAreaStock.First(c => c.MaterialsId == item.MaterialId);
                    var usedStock = false;
                    if (item.First)
                    {
                        foreach (var material in stock.Where(c => c.MaterialId == mainMaterial.MaterialId && !addMaterialList.Any(x => x.MaterialBoxNO == c.MaterialBoxNo)))
                        {
                            var check = msaterialCheckList.FirstOrDefault(c => c.MaterialId == material.MaterialId && c.BatchNumber == material.BatchNumber && c.ElementId == item.ElementId);
                            weight = (item.Diff * tempRequiredWeight) / (item.Target - (check?.Content ?? 0d) * GetReplenishmentYieldRate(baseMaterialElementes, mainMaterial.MaterialId, item.ElementId));
                            weight = weight.ToString("F2").GetDouble();
                            if (material.Stock.GetDouble() > weight)
                                continue;
                            usedStock = true;
                            addMaterialList.Add(new MaterialAmountBOM
                            {
                                MaterialId = material.MaterialId,
                                MaterialName = productDetails.First(c => c.MaterialsId == material.MaterialId).MaterialsName,
                                BatchNO = material.BatchNumber,
                                RequiredWeight = material.Stock.GetDouble(),
                                MaterialBoxNO = material.MaterialBoxNo
                            });
                            break;
                        }
                        if (!usedStock)
                        {
                            imaterial = ingredientsAreaStock.First(c => c.MaterialsId == mainMaterial.MaterialId);
                            var check = msaterialCheckList.FirstOrDefault(c => c.MaterialId == mainMaterial.MaterialId && c.BatchNumber == imaterial.BatchingBatch && c.ElementId == item.ElementId);
                            weight = (item.Diff * tempRequiredWeight) / (item.Target - (check?.Content ?? 0d) * GetReplenishmentYieldRate(baseMaterialElementes, imaterial.MaterialsId, item.ElementId));
                            weight = weight.ToString("F2").GetDouble();
                        }
                    }
                    else
                    {
                        var check = msaterialCheckList.FirstOrDefault(c => c.MaterialId == imaterial.MaterialsId && c.BatchNumber == imaterial.BatchingBatch && c.ElementId == item.ElementId);
                        content = check?.Content ?? 0;
                        weight = item.Diff * tempRequiredWeight / (100 - item.Target) * 100 / check.Content * GetReplenishmentYieldRate(baseMaterialElementes, imaterial.MaterialsId, item.ElementId);
                        weight = weight.ToString("F2").GetDouble();
                    }

                    if (!usedStock)
                    {
                        addMaterialList.Add(new MaterialAmountBOM
                        {
                            MaterialId = imaterial.MaterialsId,
                            MaterialName = productDetails.First(c => c.MaterialsId == imaterial.MaterialsId).MaterialsName,
                            BatchNO = imaterial.BatchingBatch,
                            RequiredWeight = weight,
                            MaterialBoxNO = imaterial.BatchingMaterial
                        });
                    }
                    var checkResult = CheckElement();
                    if (!checkResult.Item1)
                        return (false, checkResult.Item2, null);
                    if (checkResult.Item3.Any())
                    {
                        list = checkResult.Item3;
                        goto Retry;
                    }
                }
                var result = await CreateBill();
                return (result.Item1, result.Item2, null);

                (bool, string, List<LimitMaterialElement>) CheckElement()
                {
                    double weight = requiredWeight + addMaterialList.Sum(c => c.RequiredWeight);
                    if (weight > baseInductionFurnace.MaximumSetting.GetDouble())
                        return (false, $"补料总量【{weight}】超出感应炉最大生产量【{baseInductionFurnace.MaximumSetting}】！", null);
                    var tempElementWeight = elementsWeight.Clone();
                    addMaterialElementsRate.Clear();
                    foreach (var item in addMaterialList)
                    {
                        List<ElementRate> temp;
                        if (string.IsNullOrEmpty(item.BatchNO))
                        {
                            temp = baseMaterialElementes.Where(c => c.MaterialId == item.MaterialId).Select(c => new ElementRate
                            {
                                ElementId = c.ElementId,
                                MaterialId = c.MaterialId,
                                Content = c.Content.GetDouble()
                            }).ToList();
                        }
                        else
                            temp = msaterialCheckList.Where(c => c.MaterialId == item.MaterialId && c.BatchNumber == item.BatchNO).ToList();
                        foreach (var er in temp)
                        {
                            var addMaterialElement = addMaterialElementsRate.FirstOrDefault(c => c.ElementId == er.ElementId && c.BoxNo == item.MaterialBoxNO);
                            if (addMaterialElement == null)
                            {
                                addMaterialElementsRate.Add(new ElementRate()
                                {
                                    BatchNumber = item.BatchNO,
                                    MaterialId = item.MaterialId,
                                    ElementId = er.ElementId,
                                    Content = er.Content,
                                    BoxNo = item.MaterialBoxNO
                                });
                            }
                            var elementWeight = tempElementWeight.FirstOrDefault(c => c.ElementId == er.ElementId);
                            if (elementWeight == null)
                                continue;
                            elementWeight.Weight += item.RequiredWeight * er.Content.GetDouble() / 100;
                        }
                    }

                    var newList = new List<LimitMaterialElement>();
                    foreach (var item in tempElementWeight)
                    {
                        var bom = elementBOMList.First(c => c.ElementId == item.ElementId);
                        var limit = GetLimit(bom);
                        var rate = item.Weight * 100 / weight;

                        if (rate < limit.Item2)
                        {
                            var target = GetElementRate(bom, 1);
                            newList.Add(new LimitMaterialElement()
                            {
                                ElementId = item.ElementId,
                                MaterialId = item.MaterialId,
                                Current = rate,
                                Target = target,
                                Diff = target - rate
                            });
                        }
                        else if (rate > limit.Item1)
                        {
                            var target = GetElementRate(bom, 2);
                            newList.Add(new LimitMaterialElement
                            {
                                MaterialId = item.MaterialId,
                                ElementId = item.ElementId,
                                Current = rate,
                                Target = target,
                                Diff = rate - target,
                                First = true
                            });

                        }
                    }
                    return (true, "", newList);
                }

                async Task<(bool, string)> CreateBill()
                {
                    if (!addMaterialList.Any())
                        return (false, "没有需要补料的数据！");
                    var nowDate = DateTime.Now;
                    var no = repository.DbContext.Set<ReplenishmentList>().Count(c => c.CreateDate >= nowDate.Date && c.CreateDate < nowDate.Date.AddDays(1)) + 1;
                    var batchNo = $"B{nowDate.ToString("yyyyMMdd")}#{ifr.HeatNo}#{no.ToString().PadLeft(6, '0')}";
                    var bill = new ReplenishmentList()
                    {
                        BatchingNo = batchNo,
                        BatchNumber = ifr.BatchNumber,
                        PlannedReplenishmentQuantity = addMaterialList.Sum(c => c.RequiredWeight).ToDecimal(),
                        DeletionFlag = 0,
                        HeatNo = ifr.HeatNo,
                        IFPDId = ifpd.Id,
                        Id = Guid.NewGuid().ToString(),
                        OrderNumber = ifpd.OrderFormNo,
                        SteelWeight = ifr.MaterialWeight,
                        ReplenishmentTime = ifr.CreateDate
                    };
                    bill.SetCreateDefaultVal();
                    var notice = new NotificationForm
                    {
                        Content = $"订单号【{ifpd.OrderFormNo}】已补料，请前住【补料单查询】查看",
                        DeletionFlag = 0,
                        OrderNumber = batchNo,
                        OrderType = "3",
                        RedirectPath = "",
                        SourceOrderNumber = ifpd.OrderFormNo
                    };
                    notice.SetCreateDefaultVal();
                    var list = new List<ReplenishmentLisElement>();
                    var batchingMaterialBoxes = new List<BatchingMaterialBox>();
                    var billDetails = addMaterialList.GroupBy(c => new { c.MaterialBoxNO, c.MaterialId, c.MaterialName, c.BatchNO }).Select(c =>
                    {
                        var box = new BatchingMaterialBox()
                        {
                            MaterialBoxNo = c.Key.MaterialBoxNO,
                            Id = Guid.NewGuid().ToString(),
                            OrderNumber = bill.Id,
                            Types = 2
                        };
                        box.SetCreateDefaultVal();
                        batchingMaterialBoxes.Add(box);
                        var lkStock = stock.FirstOrDefault(x => x.MaterialBoxNo == c.Key.MaterialBoxNO);
                        var model = new ReplenishmentListDetails()
                        {
                            ReplenishmentListId = bill.Id,
                            Id = Guid.NewGuid().ToString(),
                            BatchNumber = c.Key.BatchNO,
                            MaterialId = c.Key.MaterialId,
                            MaterialName = c.Key.MaterialName,
                            IngredientsType = productDetails.First(x => x.MaterialsId == c.Key.MaterialId).Category,
                            RequiredWeight = c.Sum(x => x.RequiredWeight).ToString(),
                            State = 0,
                            MaterialBoxNo = c.Key.MaterialBoxNO,
                            Source = lkStock == null ? "2" : "1"
                        };
                        model.SetCreateDefaultVal();
                        list.AddRange(GetElemnts(model, bill.PlannedReplenishmentQuantity ?? 0 + bill.SteelWeight ?? 0));
                        return model;
                    }).ToList();
                    lock (ReplenishmentListLock)
                    {
                        //7.5 获取已生成配料单
                        lastBill = repository.DbContext.Set<ReplenishmentList>().FirstOrDefault(c => c.IFPDId == ifpd.Id && c.DeletionFlag != 1);
                        if (lastBill != null && lastBill.Enable > 0)
                        {
                            return (false, $"该生产计划生成的配料单【{lastBill.BatchingNo}】已处理，请勿重复操作！");
                        }
                        var response = new WebResponseContent();
                        response = repository.DbContextBeginTransaction(() =>
                        {
                            if (lastBill != null)
                            {
                                //7.6 删除配料单
                                lastBill.DeletionFlag = 1;
                                lastBill.SetModifyDefaultVal();
                                var type = typeof(IngredientList);
                                var listField = type.GetDeletionFileds();
                                //软删除已生成配料单
                                repository.Update(lastBill, listField.ToArray());
                                repository.DapperContext.ExcuteNonQuery("DELETE dbo.ReplenishmentLisElement  WHERE ReplenishmentLisId = @ReplenishmentLisId",
                                    new { ReplenishmentLisId = lastBill.Id });
                                repository.DapperContext.ExcuteNonQuery("DELETE BatchingMaterialBox WHERE OrderNumber = @OrderId;" +
                                    "UPDATE NotificationForm SET DeletionFlag = 1 WHERE OrderNumber = @OrderNumer  AND OrderType = 4",
                                    new { OrderId = lastBill.Id, OrderNumber = lastBill.BatchingNo });
                            }
                            //生成新的配料单
                            repository.DbContext.Entry(bill).State = EntityState.Added;
                            repository.DbContext.Entry(notice).State = EntityState.Added;
                            list.ForEach(e =>
                            {
                                repository.DbContext.Entry(e).State = EntityState.Added;
                            });
                            billDetails.ForEach(c =>
                            {
                                repository.DbContext.Entry(c).State = EntityState.Added;
                            });
                            batchingMaterialBoxes.ForEach(c =>
                            {
                                repository.DbContext.Entry(c).State = EntityState.Added;
                            });

                            repository.DbContext.SaveChanges();
                            response.OK(ResponseType.SaveSuccess);
                            return response;
                        });
                        if (response.Status)
                            sendNoticeAction?.Invoke(notice);
                        return (response.Status, response.Message);
                    }
                }

                IEnumerable<ReplenishmentLisElement> GetElemnts(ReplenishmentListDetails detail, decimal total)
                {
                    return addMaterialElementsRate.Where(c => c.BoxNo == detail.MaterialBoxNo).Select(c =>
                    {
                        var tmpWeight = (detail.RequiredWeight.ToDecimal() * c.Content.ToDecimal() / 100).ToDecimal();
                        var model = new ReplenishmentLisElement
                        {
                            Id = Guid.NewGuid().ToString(),
                            BatchNumber = c.BatchNumber,
                            MaterialId = c.MaterialId,
                            ElementId = c.ElementId,
                            ElementName = baseElements.First(x => x.Id == c.ElementId).ElementName,
                            TargetWeight = tmpWeight.ToString("F2").ToDecimal(),
                            TargetProportion = (tmpWeight * 100 / total).ToDouble().ToString("F6").ToDecimal(),
                            MaterialName = detail.MaterialName,
                            ReplenishmentLisDetailsId = detail.Id,
                            ReplenishmentLisId = detail.ReplenishmentListId,
                            ElementContent = c.Content.ToDecimal()
                        };
                        return model;
                    });
                }

                //   useType = 1 走下线  =2 走上限
                double GetElementRate(ProductElementBOM element, int useType = 0)
                {
                    double elementRate = 0d;
                    if (element.TargetProportion.HasValue)
                        elementRate = element.TargetProportion.Value.GetDouble();
                    else if (element.UpperLimitIC.HasValue)
                    {
                        var lowerLimit = 0d;
                        if (element.LowerLimitStandard.HasValue)
                            lowerLimit = element.LowerLimitIC.Value.GetDouble();
                        elementRate = (element.UpperLimitIC.Value.GetDouble() - lowerLimit) / 2 + lowerLimit;
                    }
                    else if (element.UpperLimitStandard.HasValue)
                    {
                        var lowerLimit = 0d;
                        if (element.LowerLimitStandard.HasValue)
                            lowerLimit = element.LowerLimitStandard.Value.GetDouble();
                        elementRate = (element.UpperLimitStandard.Value.GetDouble() - lowerLimit) / 2 + lowerLimit;
                    }

                    if (AppSetting.Replenishment != null)
                    {
                        if (AppSetting.Replenishment.LowerRate < 100 && useType == 1)
                        {
                            double lowerLimit = 0;
                            if (element.LowerLimitIC.HasValue)
                                lowerLimit = element.LowerLimitIC.Value.GetDouble();
                            else if (element.LowerLimitStandard.HasValue)
                                lowerLimit = element.LowerLimitStandard.Value.GetDouble();
                            elementRate = (elementRate - lowerLimit) / 100 * AppSetting.Replenishment.LowerRate + lowerLimit;
                        }
                        if (AppSetting.Replenishment.UpperRate < 100 && useType == 2)
                        {
                            double upperLimit = 0;
                            if (element.UpperLimitIC.HasValue)
                                upperLimit = element.UpperLimitIC.Value.GetDouble();
                            else if (element.UpperLimitStandard.HasValue)
                                upperLimit = element.UpperLimitStandard.Value.GetDouble();
                            elementRate = (upperLimit - elementRate) * (100 - AppSetting.Replenishment.UpperRate) / 100 + elementRate;
                        }
                    }

                    return elementRate;
                }
            }
            catch (Exception ex)
            {
                return (false, ex.Message, null);
            }
            finally
            {
                IsCanExcute = true;
            }
        }


        /// <summary>
        /// 炼钢过程中补料计算
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="id"></param>
        /// <param name="repository"></param>
        /// <param name="sendNoticeAction"></param>
        /// <returns></returns>
        public static async Task<(bool, string, List<MaterialAmountBOM>)> ReplenishmentOnProcessing<T>(this string id, IRepository<T> repository, Func<NotificationForm, Task> sendNoticeAction) where T : BaseEntity
        {
            if (!CanExcute())
            {
                return (false, "计算进行中，请勿重复操作.！", null);
            }
            try
            {
                var ifr = await repository.FindAsyncFirst<InductionFurnaceReplenishment>(c => c.Id == id);
                if (ifr == null)
                    return (false, "补料数据不存在！", null);

                if (string.IsNullOrEmpty(ifr.CheckListId))
                    return (false, "过程中补料计算缺少检化验数据！", null);

                //1.1 获取产品名称
                var productName = ifr.ProductName;
                //1.2 真实总投放量
                double requiredWeight = 0d;

                //2.0 获取产品主数据
                var product = await repository.FindAsyncFirst<ProductData>(c => c.Name == productName);
                if (product == null) return new(false, "该产品信息未配置，获取产品信息失败！", null);

                var materialQueryable = repository.DbContext.Set<MaterialMaster>().Where(c => !c.DeletionFlag.HasValue || c.DeletionFlag == 0);
                //2.1 获取产品明细物料数据
                var productDetailsQueryable = repository.DbContext.Set<ProductMaterials>().Where(c => c.ProductId == product.Id && (!c.DeletionFlag.HasValue || c.DeletionFlag == 0)).Join(materialQueryable, t => t.MaterialsId, s => s.Id, (t, s) => new
                {
                    t.MaterialsId,
                    t.MaterialsName,
                    t.ProductId,
                    t.YieldRate,
                    s.Category
                });
                var productDetails = productDetailsQueryable.ToList();
                //var productDetails = await repository.FindAsync<ProductMaterials>(c => c.ProductId == product.Id && (!c.DeletionFlag.HasValue || c.DeletionFlag == 0));
                //2.2 判断物料信息数据
                if (!productDetails.Any())
                    return new(false, "该产品物料清单信息未配置！", null);

                var ifpd = await repository.FindAsyncFirst<InductionFurnacePD>(c => c.ProductName == productName && c.BatchNumber == ifr.BatchNumber);
                if (ifpd == null)
                    return new(false, "未找到相关生产数据！", null);

                var lastBill = await repository.FindAsyncFirst<ReplenishmentList>(c => c.IFPDId == ifpd.Id && c.DeletionFlag != 1);
                if (lastBill != null && lastBill.Enable > 0)
                    return (false, $"该生产计划生成的配料单【{lastBill.BatchingNo}】已处理，请勿重复操作！", null);

                //4.0 获取元素基础数据
                var baseElements = await repository.FindAsync<BaseElement>(c => c.Enable == 1 || !c.Enable.HasValue);

                var baseMaterialElementes = await repository.FindAsync<MaterialElement>(c => productDetails.Select(x => x.MaterialsId).Contains(c.MaterialId));
                //6.0 获取产品 元素控制数据
                var elementBOMList = await repository.FindAsync<ProductElementBOM>(c => c.ProductId == product.Id && (!c.DeletionFlag.HasValue || c.DeletionFlag == 0));
                if (!elementBOMList.Any())
                    return new(false, "该产品产品元素清单信息未配置！", null);
                //6.1 设置组合元素的计算元素
                else if (elementBOMList.Any(c => !string.IsNullOrEmpty(c.CalculatingElement)))
                {
                    foreach (var item in elementBOMList.Where(c => !string.IsNullOrEmpty(c.CalculatingElement)))
                    {
                        var baseElement = baseElements.FirstOrDefault(c => c.ElementSymbols.Trim().ToLower() == item.CalculatingElement.Trim().ToLower());
                        if (baseElement == null)
                            return (false, $"未找到该计算元素【{item.CalculatingElement}】的基础元素配置信息！", null);
                        item.ElementId = baseElement.Id;
                    }
                }

                var checkList = repository.DbContext.Set<CheckList>().FirstOrDefault(c => c.CheckListNo == ifr.CheckListId);
                if (checkList == null)
                    return (false, "未查询相关检化验信息！", null);


                //8.1 获取物料主元素信息
                var mainComponents = baseMaterialElementes.GroupBy(c => c.MaterialId).Select(c => c.ToList().OrderByDescending(x => x.Content).FirstOrDefault()).ToList();
                //8.2 不可检出项
                var elementBOMs = elementBOMList.Where(c => !mainComponents.Any(x => x.ElementId == c.ElementId)).ToList();

                var checkListDetails = repository.DbContext.Set<CheckListDetailed>().Where(c => c.CheckListId == checkList.Id).ToList();
                var mainMaterialList = elementBOMList.Join(baseMaterialElementes,
                s => s.ElementId, t => t.ElementId, (s, t) => new
                {
                    t.MaterialId,
                    s.TargetProportion,
                    t.Content,
                    t.ElementId
                }).OrderByDescending(c => c.TargetProportion).ThenByDescending(c => c.Content);

                //获取当前工艺投放料详情，用来计算比例
                var placingMaterials = await (from pm in repository.DbContext.Set<PlacingMaterials>()
                                              join pi in repository.DbContext.Set<ProductionInspection>().Where(c => c.ProductId == product.Id && c.Sort <= checkList.Step)
                                              on pm.PIId equals pi.Id
                                              select pm).ToListAsync();

                //获取配料单真实配料数据
                var realMaterials = (from ild in repository.DbContext.Set<IngredientListDetails>()
                                     join il in repository.DbContext.Set<IngredientList>().Where(c => c.Heat == ifr.Heat && c.OrderNumber == ifr.OrderFormNo && c.DeletionFlag != 1)
                                     on ild.IngredientListId equals il.Id
                                     select ild).ToList().GroupBy(c => new { c.MaterialId, c.MaterialName, c.IngredientsType }).Select(c => new
                                     {
                                         c.Key.MaterialId,
                                         c.Key.MaterialName,
                                         c.Key.IngredientsType,
                                         ActualWeight = c.Sum(x => x.ActualWeight.GetDouble())
                                     }).ToList();

                //当前生产工艺真实总投放量
                requiredWeight = realMaterials.Where(c => placingMaterials.Any(x => x.PlacingItemId == c.MaterialId) || c.IngredientsType >= 2).Sum(x => x.ActualWeight.GetDouble());
                var mainMaterial = mainMaterialList.FirstOrDefault();
                var addMaterialList = new List<MaterialAmountBOM>();
                //需要补料的元素
                var list = new List<LimitMaterialElement>();
                var elementsWeight = new List<ElementWeight>();
                var addMaterialElementsRate = new List<ElementRate>();

                //获取需要计算是否补料的物料信息
                var needReplenishmentMaterials = mainComponents.Where(c => c.MaterialId != mainMaterial.MaterialId && placingMaterials.Any(x => x.PlacingItemId == c.MaterialId)).ToList();
                foreach (var item in needReplenishmentMaterials)
                {
                    var element = checkListDetails.FirstOrDefault(c => c.ElementId == item.ElementId);
                    var bom = placingMaterials.FirstOrDefault(c => c.PlacingItemId == item.MaterialId);
                    if (bom == null || !bom.LowerLimit.HasValue || bom.LowerLimit <= 0
                        || !bom.UpperLimit.HasValue || bom.UpperLimit <= 0 || bom.LowerLimit > bom.UpperLimit)
                    {
                        return (false, $"该物料【{productDetails.First(c => c.MaterialsId == item.MaterialId).MaterialsName}】工艺步骤【{checkList.Step}】配置异常！", null);
                    }

                    var limitResult = element.Result.GetDouble();
                    elementsWeight.Add(new ElementWeight
                    {
                        ElementId = element.ElementId,
                        MaterialId = item.MaterialId,
                        Weight = requiredWeight * limitResult / 100
                    });

                    if (limitResult < bom.LowerLimit.GetDouble())
                    {
                        var target = GetElementRate(new ProductElementBOM
                        {
                            TargetProportion = (bom.UpperLimit + bom.LowerLimit) / 2,
                            LowerLimitIC = bom.LowerLimit,
                            UpperLimitIC = bom.UpperLimit
                        }, 1);
                        list.Add(new LimitMaterialElement
                        {
                            MaterialId = item.MaterialId,
                            ElementId = item.ElementId,
                            Current = limitResult,
                            Target = target,
                            Diff = target - limitResult
                        });
                    }
                    else if (limitResult > bom.UpperLimit.GetDouble())
                    {
                        var target = GetElementRate(new ProductElementBOM
                        {
                            TargetProportion = (bom.UpperLimit + bom.LowerLimit) / 2,
                            LowerLimitIC = bom.LowerLimit,
                            UpperLimitIC = bom.UpperLimit
                        }, 2);
                        list.Add(new LimitMaterialElement
                        {
                            MaterialId = item.MaterialId,
                            ElementId = item.ElementId,
                            Current = limitResult,
                            Target = target,
                            Diff = limitResult - target,
                            First = true
                        });

                    }
                }

                if (!list.Any())
                    return (false, "没有需要补料的数据！", null);

                var lastBillStcokMaterialBox = new List<string>();

                var sQueryable = repository.DbContext.Set<MaterialBox>().Where(c => c.OrderNumber == ifr.OrderFormNo && (c.State == 10 || c.State == 99));
                if (Sys_ConfigKeys.LockStock.GetConfig() == "1")
                {
                    var lastBillId = lastBill?.Id ?? "";
                    lastBillStcokMaterialBox = await (from bmb in repository.DbContext.Set<BatchingMaterialBox>().Where(c => c.OrderNumber != lastBillId)
                                     join mb in sQueryable
                                     on bmb.MaterialBoxNo equals mb.BoxNo
                                     select bmb.MaterialBoxNo).ToListAsync();

                    //lastBillStcokMaterialBox = repository.DbContext.Set<BatchingMaterialBox>().Join(sQueryable, t => t.MaterialBoxNo, s => s.BoxNo, (t, s) => new { t.MaterialBoxNo, t.OrderNumber, t.Types }).Where(c => c.OrderNumber != (lastBill.Id ?? "")).Select(c => c.MaterialBoxNo).ToList();
                }

                //3.0 获取计划单 物料库存数据
                var stockQueryable = sQueryable.Select(c => new
                {
                    c.BatchNumber,
                    c.MaterialId,
                    Stock = c.MaterialWeight,
                    State = c.State ?? 0,
                    MaterialBoxNo = c.BoxNo
                });
                var stock = stockQueryable.ToList();
                foreach (var item in lastBillStcokMaterialBox)
                {
                    var tmpStock = stock?.FirstOrDefault(c => c.MaterialBoxNo == item);
                    if (tmpStock != null)
                        stock.Remove(tmpStock);
                }
                //if (!stock.Any())
                //    return new(false, "计划单物料库存数据异常！", null);
                //3.1 判断是否存在无库存的物料
                //var stockGroup = stock.GroupBy(c => c.MaterialId);
                //var notExistsMaterial = stockGroup.Any(c => c.Key == mainMaterial.MaterialId);
                //if (!notExistsMaterial)
                //    return new(false, $"缺少【{productDetails.First(c => c.MaterialsId == mainMaterial.MaterialId).MaterialsName}】在库库存信息！", null);


                //配料区库存 和 配料备料区库存
                var ingredientsAreaStockQueryable = repository.DbContext.Set<IngredientsArea>().Join(productDetailsQueryable, t => t.BatchingMaterials, s => s.MaterialsId, (t, s) => new
                {
                    s.MaterialsId,
                    s.MaterialsName,
                    t.BatchingBatch,
                    t.BatchingRemainingWeight,
                    t.MaterialPreparationBatch,
                    t.MaterialPreparationWeight,
                    t.MaterialPreparationMaterial,
                    t.BatchingMaterial
                });

                var checkListQeryable = stockQueryable.Select(c => new { c.MaterialId, c.BatchNumber })
                    .Union(ingredientsAreaStockQueryable.Select(c => new { MaterialId = c.MaterialsId, BatchNumber = c.BatchingBatch }))
                    .Union(ingredientsAreaStockQueryable.Select(c => new { MaterialId = c.MaterialsId, BatchNumber = c.MaterialPreparationBatch }))
                    .GroupBy(c => new { c.MaterialId, c.BatchNumber })
                    .Select(c => new { c.Key.MaterialId, c.Key.BatchNumber });

                var ingredientsAreaStock = ingredientsAreaStockQueryable.ToList();
                //5.0 获取物料同批次检化验单号
                var queryableList = repository.DbContext.Set<CheckList>().Where(c => c.Category == 1).Join(checkListQeryable, s => new { s.MaterialId, BatchNumber = s.BatchNo }, t => new { t.MaterialId, t.BatchNumber }, (s, t) => new
                {
                    t.BatchNumber,
                    t.MaterialId,
                    s.Id,
                    s.GetTime
                }
                ).ToList();

                //5.1 同批次物料检化验结果取最后一次
                var materialCheckList = queryableList.GroupBy(c => new { c.MaterialId, c.BatchNumber }).Select(c => new
                {
                    c.Key.MaterialId,
                    c.Key.BatchNumber,
                    c.OrderByDescending(x => x.GetTime).First().Id
                }).ToList();
                //5.2 获取检测单明细
                var materialCheckListDetaild = await repository.FindAsync<CheckListDetailed>(c => materialCheckList.Select(x => x.Id).Contains(c.CheckListId));
                //5.3 关联获取物料批次检化验结果 元素成分占比
                var msaterialCheckList = materialCheckListDetaild.Join(materialCheckList, s => s.CheckListId, t => t.Id, (s, t) => new ElementRate
                {
                    MaterialId = t.MaterialId,
                    BatchNumber = t.BatchNumber,
                    ElementId = s.ElementId,
                    Check = true,
                    Content = s.Result.GetDouble()
                }).ToList();

                var currentMainMaterialElementRate = checkListDetails.First(c => c.ElementId == mainMaterial.ElementId);
                var baseInductionFurnace = await repository.FindAsyncFirst<Base_InductionFurnace>(c => c.Id == ifr.HeatId);
                Retry:
                var tempRequiredWeight = requiredWeight + addMaterialList.Sum(c => c.RequiredWeight);
                foreach (var item in list.OrderByDescending(c => c.First).ThenByDescending(c => c.Diff).ToList())
                {
                    var weight = 0d;
                    var content = 0d;
                    var imaterial = ingredientsAreaStock.First(c => c.MaterialsId == item.MaterialId);
                    var usedStock = false;
                    if (item.First)
                    {
                        foreach (var material in stock.Where(c => c.MaterialId == mainMaterial.MaterialId && !addMaterialList.Any(x => x.MaterialBoxNO == c.MaterialBoxNo)))
                        {
                            var check = msaterialCheckList.FirstOrDefault(c => c.MaterialId == material.MaterialId && c.BatchNumber == material.BatchNumber && c.ElementId == item.ElementId);
                            weight = (item.Diff * tempRequiredWeight) / (item.Target - (check?.Content ?? 0d) * GetReplenishmentYieldRate(baseMaterialElementes, mainMaterial.MaterialId, item.ElementId));
                            weight = weight.ToString("F2").GetDouble();
                            if (material.Stock.GetDouble() > weight)
                                continue;
                            usedStock = true;
                            addMaterialList.Add(new MaterialAmountBOM
                            {
                                MaterialId = material.MaterialId,
                                MaterialName = productDetails.First(c => c.MaterialsId == material.MaterialId).MaterialsName,
                                BatchNO = material.BatchNumber,
                                RequiredWeight = material.Stock.GetDouble(),
                                MaterialBoxNO = material.MaterialBoxNo
                            });
                            break;
                        }
                        if (!usedStock)
                        {
                            imaterial = ingredientsAreaStock.First(c => c.MaterialsId == mainMaterial.MaterialId);
                            var check = msaterialCheckList.FirstOrDefault(c => c.MaterialId == mainMaterial.MaterialId && c.BatchNumber == imaterial.BatchingBatch && c.ElementId == item.ElementId);
                            weight = (item.Diff * tempRequiredWeight) / (item.Target - (check?.Content ?? 0d) * GetReplenishmentYieldRate(baseMaterialElementes, imaterial.MaterialsId, item.ElementId));
                            weight = weight.ToString("F2").GetDouble();
                        }
                    }
                    else
                    {
                        var check = msaterialCheckList.FirstOrDefault(c => c.MaterialId == imaterial.MaterialsId && c.BatchNumber == imaterial.BatchingBatch && c.ElementId == item.ElementId);
                        content = check?.Content ?? 0;
                        weight = item.Diff * tempRequiredWeight / (100 - item.Target) * 100 / check.Content * GetReplenishmentYieldRate(baseMaterialElementes, imaterial.MaterialsId, item.ElementId);
                        weight = weight.ToString("F2").GetDouble();
                    }

                    if (!usedStock)
                    {
                        addMaterialList.Add(new MaterialAmountBOM
                        {
                            MaterialId = imaterial.MaterialsId,
                            MaterialName = productDetails.First(c => c.MaterialsId == imaterial.MaterialsId).MaterialsName,
                            BatchNO = imaterial.BatchingBatch,
                            RequiredWeight = weight,
                            MaterialBoxNO = imaterial.BatchingMaterial
                        });
                    }
                    var checkResult = CheckElement();
                    if (!checkResult.Item1)
                        return (false, checkResult.Item2, null);
                    if (checkResult.Item3.Any())
                    {
                        list = checkResult.Item3;
                        goto Retry;
                    }
                }
                var result = await CreateBill();
                return (result.Item1, result.Item2, null);

                (bool, string, List<LimitMaterialElement>) CheckElement()
                {
                    double weight = requiredWeight + addMaterialList.Sum(c => c.RequiredWeight);
                    if (weight > baseInductionFurnace.MaximumSetting.GetDouble())
                        return (false, $"补料总量【{weight}】超出感应炉最大生产量【{baseInductionFurnace.MaximumSetting}】！", null);
                    var tempElementWeight = elementsWeight.Clone();
                    addMaterialElementsRate.Clear();
                    foreach (var item in addMaterialList)
                    {
                        List<ElementRate> temp;
                        if (string.IsNullOrEmpty(item.BatchNO))
                        {
                            temp = baseMaterialElementes.Where(c => c.MaterialId == item.MaterialId).Select(c => new ElementRate
                            {
                                ElementId = c.ElementId,
                                MaterialId = c.MaterialId,
                                Content = c.Content.GetDouble()
                            }).ToList();
                        }
                        else
                            temp = msaterialCheckList.Where(c => c.MaterialId == item.MaterialId && c.BatchNumber == item.BatchNO).ToList();
                        foreach (var er in temp)
                        {
                            var addMaterialElement = addMaterialElementsRate.FirstOrDefault(c => c.ElementId == er.ElementId && c.BoxNo == item.MaterialBoxNO);
                            if (addMaterialElement == null)
                            {
                                addMaterialElementsRate.Add(new ElementRate()
                                {
                                    BatchNumber = item.BatchNO,
                                    MaterialId = item.MaterialId,
                                    ElementId = er.ElementId,
                                    Content = er.Content,
                                    BoxNo = item.MaterialBoxNO
                                });
                            }
                            var elementWeight = tempElementWeight.FirstOrDefault(c => c.ElementId == er.ElementId);
                            if (elementWeight == null)
                                continue;
                            elementWeight.Weight += item.RequiredWeight * er.Content.GetDouble() / 100;
                        }
                    }

                    var newList = new List<LimitMaterialElement>();
                    foreach (var item in tempElementWeight)
                    {
                        var bom = placingMaterials.FirstOrDefault(c => c.PlacingItemId == item.MaterialId);
                        if (bom == null || !bom.LowerLimit.HasValue || bom.LowerLimit <= 0
                            || !bom.UpperLimit.HasValue || bom.UpperLimit <= 0 || bom.LowerLimit > bom.UpperLimit)
                        {
                            return (false, $"该物料【{productDetails.First(c => c.MaterialsId == item.MaterialId).MaterialsName}】工艺步骤【{checkList.Step}】配置异常！", null);
                        }

                        var rate = item.Weight * 100 / weight;

                        if (rate < bom.LowerLimit.GetDouble())
                        {
                            var target = GetElementRate(new ProductElementBOM
                            {
                                TargetProportion = (bom.UpperLimit + bom.LowerLimit) / 2,
                                LowerLimitIC = bom.LowerLimit,
                                UpperLimitIC = bom.UpperLimit
                            }, 1);
                            list.Add(new LimitMaterialElement
                            {
                                MaterialId = item.MaterialId,
                                ElementId = item.ElementId,
                                Current = rate,
                                Target = target,
                                Diff = target - rate
                            });
                        }
                        else if (rate > bom.UpperLimit.GetDouble())
                        {
                            var target = GetElementRate(new ProductElementBOM
                            {
                                TargetProportion = (bom.UpperLimit + bom.LowerLimit) / 2,
                                LowerLimitIC = bom.LowerLimit,
                                UpperLimitIC = bom.UpperLimit
                            }, 2);
                            list.Add(new LimitMaterialElement
                            {
                                MaterialId = item.MaterialId,
                                ElementId = item.ElementId,
                                Current = rate,
                                Target = target,
                                Diff = rate - target,
                                First = true
                            });

                        }
                    }
                    return (true, "", newList);
                }

                async Task<(bool, string)> CreateBill()
                {
                    if (!addMaterialList.Any())
                        return (false, "没有需要补料的数据！");
                    var nowDate = DateTime.Now;
                    var no = repository.DbContext.Set<ReplenishmentList>().Count(c => c.CreateDate >= nowDate.Date && c.CreateDate < nowDate.Date.AddDays(1)) + 1;
                    var batchNo = $"B{nowDate.ToString("yyyyMMdd")}#{ifr.HeatNo}#{no.ToString().PadLeft(6, '0')}";
                    var bill = new ReplenishmentList()
                    {
                        BatchingNo = batchNo,
                        BatchNumber = ifr.BatchNumber,
                        PlannedReplenishmentQuantity = addMaterialList.Sum(c => c.RequiredWeight).ToDecimal(),
                        DeletionFlag = 0,
                        HeatNo = ifr.HeatNo,
                        IFPDId = ifpd.Id,
                        Id = Guid.NewGuid().ToString(),
                        OrderNumber = ifpd.OrderFormNo,
                        SteelWeight = ifr.MaterialWeight,
                        ReplenishmentTime = ifr.CreateDate
                    };
                    bill.SetCreateDefaultVal();
                    var notice = new NotificationForm
                    {
                        Content = $"订单号【{ifpd.OrderFormNo}】已补料，请前住【补料单查询】查看",
                        DeletionFlag = 0,
                        OrderNumber = batchNo,
                        OrderType = "3",
                        RedirectPath = "",
                        SourceOrderNumber = ifpd.OrderFormNo
                    };
                    notice.SetCreateDefaultVal();
                    var list = new List<ReplenishmentLisElement>();
                    var batchingMaterialBoxes = new List<BatchingMaterialBox>();
                    var billDetails = addMaterialList.GroupBy(c => new { c.MaterialBoxNO, c.MaterialId, c.MaterialName, c.BatchNO }).Select(c =>
                    {
                        var box = new BatchingMaterialBox()
                        {
                            MaterialBoxNo = c.Key.MaterialBoxNO,
                            Id = Guid.NewGuid().ToString(),
                            OrderNumber = bill.Id,
                            Types = 2
                        };
                        box.SetCreateDefaultVal();
                        batchingMaterialBoxes.Add(box);
                        var lkStock = stock.FirstOrDefault(x => x.MaterialBoxNo == c.Key.MaterialBoxNO);
                        var model = new ReplenishmentListDetails()
                        {
                            ReplenishmentListId = bill.Id,
                            Id = Guid.NewGuid().ToString(),
                            BatchNumber = c.Key.BatchNO,
                            MaterialId = c.Key.MaterialId,
                            MaterialName = c.Key.MaterialName,
                            IngredientsType = productDetails.First(x => x.MaterialsId == c.Key.MaterialId).Category,
                            RequiredWeight = c.Sum(x => x.RequiredWeight).ToString(),
                            State = 0,
                            MaterialBoxNo = c.Key.MaterialBoxNO,
                            Source = lkStock == null ? "2" : "1"
                        };
                        model.SetCreateDefaultVal();
                        list.AddRange(GetElemnts(model, bill.PlannedReplenishmentQuantity ?? 0 + bill.SteelWeight ?? 0));
                        return model;
                    }).ToList();
                    lock (ReplenishmentListLock)
                    {
                        //7.5 获取已生成配料单
                        lastBill = repository.DbContext.Set<ReplenishmentList>().FirstOrDefault(c => c.IFPDId == ifpd.Id && c.DeletionFlag != 1);
                        if (lastBill != null && lastBill.Enable > 0)
                        {
                            return (false, $"该生产计划生成的配料单【{lastBill.BatchingNo}】已处理，请勿重复操作！");
                        }
                        var response = new WebResponseContent();
                        response = repository.DbContextBeginTransaction(() =>
                        {
                            if (lastBill != null)
                            {
                                //7.6 删除配料单
                                lastBill.DeletionFlag = 1;
                                lastBill.SetModifyDefaultVal();
                                var type = typeof(IngredientList);
                                var listField = type.GetDeletionFileds();
                                //软删除已生成配料单
                                repository.Update(lastBill, listField.ToArray());
                                repository.DapperContext.ExcuteNonQuery("DELETE dbo.ReplenishmentLisElement  WHERE ReplenishmentLisId = @ReplenishmentLisId",
                                    new { ReplenishmentLisId = lastBill.Id });
                                repository.DapperContext.ExcuteNonQuery("DELETE BatchingMaterialBox WHERE OrderNumber = @OrderId;" +
                                    "UPDATE NotificationForm SET DeletionFlag = 1 WHERE OrderNumber = @OrderNumer  AND OrderType = 4",
                                    new { OrderId = lastBill.Id, OrderNumber = lastBill.BatchingNo });
                            }
                            //生成新的配料单
                            repository.DbContext.Entry(bill).State = EntityState.Added;
                            repository.DbContext.Entry(notice).State = EntityState.Added;
                            list.ForEach(e =>
                            {
                                repository.DbContext.Entry(e).State = EntityState.Added;
                            });
                            billDetails.ForEach(c =>
                            {
                                repository.DbContext.Entry(c).State = EntityState.Added;
                            });
                            batchingMaterialBoxes.ForEach(c =>
                            {
                                repository.DbContext.Entry(c).State = EntityState.Added;
                            });

                            repository.DbContext.SaveChanges();
                            response.OK(ResponseType.SaveSuccess);
                            return response;
                        });
                        if (response.Status)
                            sendNoticeAction?.Invoke(notice);
                        return (response.Status, response.Message);
                    }
                }

                IEnumerable<ReplenishmentLisElement> GetElemnts(ReplenishmentListDetails detail, decimal total)
                {
                    return addMaterialElementsRate.Where(c => c.BoxNo == detail.MaterialBoxNo).Select(c =>
                    {
                        var tmpWeight = (detail.RequiredWeight.ToDecimal() * c.Content.ToDecimal() / 100).ToDecimal();
                        var model = new ReplenishmentLisElement
                        {
                            Id = Guid.NewGuid().ToString(),
                            BatchNumber = c.BatchNumber,
                            MaterialId = c.MaterialId,
                            ElementId = c.ElementId,
                            ElementName = baseElements.First(x => x.Id == c.ElementId).ElementName,
                            TargetWeight = tmpWeight.ToString("F2").ToDecimal(),
                            TargetProportion = (tmpWeight * 100 / total).ToDouble().ToString("F6").ToDecimal(),
                            MaterialName = detail.MaterialName,
                            ReplenishmentLisDetailsId = detail.Id,
                            ReplenishmentLisId = detail.ReplenishmentListId,
                            ElementContent = c.Content.ToDecimal()
                        };
                        return model;
                    });
                }

                //   useType = 1 走下线  =2 走上限
                double GetElementRate(ProductElementBOM element, int useType = 0)
                {
                    double elementRate = 0d;
                    if (element.TargetProportion.HasValue)
                        elementRate = element.TargetProportion.Value.GetDouble();
                    else if (element.UpperLimitIC.HasValue)
                    {
                        var lowerLimit = 0d;
                        if (element.LowerLimitStandard.HasValue)
                            lowerLimit = element.LowerLimitIC.Value.GetDouble();
                        elementRate = (element.UpperLimitIC.Value.GetDouble() - lowerLimit) / 2 + lowerLimit;
                    }
                    else if (element.UpperLimitStandard.HasValue)
                    {
                        var lowerLimit = 0d;
                        if (element.LowerLimitStandard.HasValue)
                            lowerLimit = element.LowerLimitStandard.Value.GetDouble();
                        elementRate = (element.UpperLimitStandard.Value.GetDouble() - lowerLimit) / 2 + lowerLimit;
                    }

                    if (AppSetting.Replenishment != null)
                    {
                        if (AppSetting.Replenishment.LowerRate < 100 && useType == 1)
                        {
                            double lowerLimit = 0;
                            if (element.LowerLimitIC.HasValue)
                                lowerLimit = element.LowerLimitIC.Value.GetDouble();
                            else if (element.LowerLimitStandard.HasValue)
                                lowerLimit = element.LowerLimitStandard.Value.GetDouble();
                            elementRate = (elementRate - lowerLimit) / 100 * AppSetting.Replenishment.LowerRate + lowerLimit;
                        }
                        if (AppSetting.Replenishment.UpperRate < 100 && useType == 2)
                        {
                            double upperLimit = 0;
                            if (element.UpperLimitIC.HasValue)
                                upperLimit = element.UpperLimitIC.Value.GetDouble();
                            else if (element.UpperLimitStandard.HasValue)
                                upperLimit = element.UpperLimitStandard.Value.GetDouble();
                            elementRate = (upperLimit - elementRate) * (100 - AppSetting.Replenishment.UpperRate) / 100 + elementRate;
                        }
                    }

                    return elementRate;
                }
            }
            catch (Exception ex)
            {
                return (false, ex.Message, null);
            }
            finally
            {
                IsCanExcute = true;
            }
        }


        private static double GetReplenishmentYieldRate(List<MaterialElement> baseMaterialElements, string materialId, string elementId)
        {
            var element = baseMaterialElements.FirstOrDefault(c => c.MaterialId == materialId && c.ElementId == elementId);
            if (element == null) return 1;
            if (element.ReplenishmentYieldRate.HasValue) return element.ReplenishmentYieldRate.Value.GetDouble() / 100;
            return 1;
        }
        #endregion
    }

}
